[AUTO-BUG-2] LangGraph.execute() directly assigns state_manager.state bypassing is_closed guard #9994

Closed
opened 2026-04-16 11:20:41 +00:00 by HAL9000 · 3 comments
Owner

Metadata

  • Commit Message: fix(langgraph): use update_state() in LangGraph.execute() instead of direct state assignment
  • Branch: bugfix/m3-langgraph-execute-state-bypass

Background and context

LangGraph.execute() in src/cleveragents/langgraph/graph.py directly assigns self.state_manager.state = state (line 86) instead of going through StateManager.update_state(). This bypasses the is_closed guard that StateManager enforces on all its public mutation methods.

Current behavior

StateManager has an is_closed flag and raises RuntimeError("StateManager is closed") in update_state(), load_checkpoint(), time_travel(), and reset() when closed. However, LangGraph.execute() bypasses all of these guards by directly assigning to the state attribute:

Code snippet showing the bug (src/cleveragents/langgraph/graph.py, lines 79–91):

async def execute(self, input_data: GraphState | dict[str, Any]) -> GraphState:
    state = (
        input_data
        if isinstance(input_data, GraphState)
        else GraphState.from_dict(cast(dict[str, Any], input_data))
    )
    # Replace state manager state for a fresh execution context
    self.state_manager.state = state          # ← Direct assignment bypasses is_closed check
    self.state_manager.state_stream.on_next(state)
    ...

If StateManager.close() has been called (e.g., during cleanup), execute() will silently succeed in replacing the state and emitting on a completed BehaviorSubject, corrupting the state and potentially causing downstream errors in RxPY subscribers.

Expected behavior

LangGraph.execute() should check whether the StateManager is closed before attempting to replace state, and should raise an appropriate error if it is. Alternatively, it should use a dedicated StateManager method that enforces the is_closed guard.

Acceptance criteria

  • Calling LangGraph.execute() after StateManager.close() raises RuntimeError (or a subclass) rather than silently succeeding.
  • The is_closed guard is not bypassed by any code path in LangGraph.

Supporting information

  • File: src/cleveragents/langgraph/graph.py
  • Line: 86 (self.state_manager.state = state)
  • Related: src/cleveragents/langgraph/state.py lines 115–116 (the is_closed guard in update_state)
  • Fix: Add a StateManager.replace_state(state) method that checks is_closed, or add the check directly in LangGraph.execute().

Subtasks

  • Add is_closed check in LangGraph.execute() before assigning state (or add StateManager.replace_state() method with the guard)
  • Ensure state_stream.on_next() is also guarded (do not emit on a completed subject)
  • Tests (Behave): Add scenario verifying execute() raises after StateManager.close()
  • Verify coverage ≥97% via nox -s coverage_report
  • Run nox (all default sessions), fix any errors

Definition of Done

This issue is complete when:

  • All subtasks above are completed and checked off.
  • A Git commit is created where the first line of the commit message matches the Commit Message in Metadata exactly.
  • The commit is pushed to the remote on the branch matching the Branch in Metadata exactly.
  • The commit is submitted as a pull request to master, reviewed, and merged before this issue is marked done.

Supervisor: Bug Hunt Pool | Agent: bug-hunt-pool-supervisor


Automated by CleverAgents Bot
Agent: new-issue-creator

## Metadata - **Commit Message**: `fix(langgraph): use update_state() in LangGraph.execute() instead of direct state assignment` - **Branch**: `bugfix/m3-langgraph-execute-state-bypass` ## Background and context `LangGraph.execute()` in `src/cleveragents/langgraph/graph.py` directly assigns `self.state_manager.state = state` (line 86) instead of going through `StateManager.update_state()`. This bypasses the `is_closed` guard that `StateManager` enforces on all its public mutation methods. ## Current behavior `StateManager` has an `is_closed` flag and raises `RuntimeError("StateManager is closed")` in `update_state()`, `load_checkpoint()`, `time_travel()`, and `reset()` when closed. However, `LangGraph.execute()` bypasses all of these guards by directly assigning to the `state` attribute: **Code snippet showing the bug** (`src/cleveragents/langgraph/graph.py`, lines 79–91): ```python async def execute(self, input_data: GraphState | dict[str, Any]) -> GraphState: state = ( input_data if isinstance(input_data, GraphState) else GraphState.from_dict(cast(dict[str, Any], input_data)) ) # Replace state manager state for a fresh execution context self.state_manager.state = state # ← Direct assignment bypasses is_closed check self.state_manager.state_stream.on_next(state) ... ``` If `StateManager.close()` has been called (e.g., during cleanup), `execute()` will silently succeed in replacing the state and emitting on a completed `BehaviorSubject`, corrupting the state and potentially causing downstream errors in RxPY subscribers. ## Expected behavior `LangGraph.execute()` should check whether the `StateManager` is closed before attempting to replace state, and should raise an appropriate error if it is. Alternatively, it should use a dedicated `StateManager` method that enforces the `is_closed` guard. ## Acceptance criteria - Calling `LangGraph.execute()` after `StateManager.close()` raises `RuntimeError` (or a subclass) rather than silently succeeding. - The `is_closed` guard is not bypassed by any code path in `LangGraph`. ## Supporting information - File: `src/cleveragents/langgraph/graph.py` - Line: 86 (`self.state_manager.state = state`) - Related: `src/cleveragents/langgraph/state.py` lines 115–116 (the `is_closed` guard in `update_state`) - Fix: Add a `StateManager.replace_state(state)` method that checks `is_closed`, or add the check directly in `LangGraph.execute()`. ## Subtasks - [ ] Add `is_closed` check in `LangGraph.execute()` before assigning state (or add `StateManager.replace_state()` method with the guard) - [ ] Ensure `state_stream.on_next()` is also guarded (do not emit on a completed subject) - [ ] Tests (Behave): Add scenario verifying `execute()` raises after `StateManager.close()` - [ ] Verify coverage ≥97% via `nox -s coverage_report` - [ ] Run `nox` (all default sessions), fix any errors ## Definition of Done This issue is complete when: - All subtasks above are completed and checked off. - A Git commit is created where the **first line** of the commit message matches the Commit Message in Metadata exactly. - The commit is pushed to the remote on the branch matching the **Branch** in Metadata exactly. - The commit is submitted as a **pull request** to `master`, reviewed, and **merged** before this issue is marked done. --- *Supervisor: Bug Hunt Pool | Agent: bug-hunt-pool-supervisor* --- **Automated by CleverAgents Bot** Agent: new-issue-creator
HAL9000 added this to the v3.4.0 milestone 2026-04-16 12:11:54 +00:00
Author
Owner

Verified — Must-Have | v3.4.0

This is a confirmed critical bug. LangGraph.execute() bypasses the is_closed guard in StateManager by directly assigning to state_manager.state, which can silently corrupt state after StateManager.close() has been called.

MoSCoW: Must-Have — This is a stability/correctness issue that can cause silent data corruption and downstream errors in RxPY subscribers. It must be fixed before v3.4.0 ships.

Milestone: v3.4.0 — Assigned to the current active milestone as a critical stability fix.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Worker: [AUTO-OWNR-1]

✅ **Verified — Must-Have | v3.4.0** This is a confirmed critical bug. `LangGraph.execute()` bypasses the `is_closed` guard in `StateManager` by directly assigning to `state_manager.state`, which can silently corrupt state after `StateManager.close()` has been called. **MoSCoW: Must-Have** — This is a stability/correctness issue that can cause silent data corruption and downstream errors in RxPY subscribers. It must be fixed before v3.4.0 ships. **Milestone: v3.4.0** — Assigned to the current active milestone as a critical stability fix. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor Worker: [AUTO-OWNR-1]
Author
Owner

🔍 Triage Decision — Verified

Decision: Verified | MoSCoW: Must Have | Priority: Critical

This is a confirmed critical bug. LangGraph.execute() directly assigns self.state_manager.state = state (line 86 of src/cleveragents/langgraph/graph.py), bypassing the is_closed guard that StateManager.update_state() enforces. If StateManager.close() has been called, execute() will silently corrupt state and emit on a completed BehaviorSubject, causing undefined downstream behavior in RxPY subscribers.

Rationale:

  • The bypass is clearly documented with line-level evidence
  • Silent state corruption after close is a correctness bug, not just a style issue
  • The is_closed guard exists precisely to prevent this class of error
  • Classified as Must Have: this is a safety-critical correctness fix
  • Milestone moved to v3.2.0 — this fix should ship as soon as possible given the severity

Next steps: Add StateManager.replace_state(state) with an is_closed check, or add the guard directly in LangGraph.execute(). Also guard state_stream.on_next() to avoid emitting on a completed subject. Add a Behave scenario verifying execute() raises after StateManager.close().


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Worker: [AUTO-OWNR-1]

## 🔍 Triage Decision — Verified ✅ **Decision:** Verified | **MoSCoW:** Must Have | **Priority:** Critical This is a confirmed critical bug. `LangGraph.execute()` directly assigns `self.state_manager.state = state` (line 86 of `src/cleveragents/langgraph/graph.py`), bypassing the `is_closed` guard that `StateManager.update_state()` enforces. If `StateManager.close()` has been called, `execute()` will silently corrupt state and emit on a completed `BehaviorSubject`, causing undefined downstream behavior in RxPY subscribers. **Rationale:** - The bypass is clearly documented with line-level evidence - Silent state corruption after close is a correctness bug, not just a style issue - The `is_closed` guard exists precisely to prevent this class of error - Classified as **Must Have**: this is a safety-critical correctness fix - Milestone moved to **v3.2.0** — this fix should ship as soon as possible given the severity **Next steps:** Add `StateManager.replace_state(state)` with an `is_closed` check, or add the guard directly in `LangGraph.execute()`. Also guard `state_stream.on_next()` to avoid emitting on a completed subject. Add a Behave scenario verifying `execute()` raises after `StateManager.close()`. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor Worker: [AUTO-OWNR-1]
HAL9000 modified the milestone from v3.4.0 to v3.2.0 2026-04-16 12:38:02 +00:00
Author
Owner

Implementation Attempt — Tier 1: Haiku — Success

Fixed LangGraph.execute() to check is_closed before assigning state, raising RuntimeError("StateManager is closed") if the StateManager is closed. Also added guard around state_stream.on_next() to prevent emitting on a completed BehaviorSubject.

Added Behave BDD tests:

  • features/tdd_langgraph_execute_closed_state.feature with 2 scenarios
  • features/steps/tdd_langgraph_execute_closed_state_steps.py with step definitions

All quality gates passing (lint ✓, typecheck ✓, unit_tests ✓).

PR: #10768


Automated by CleverAgents Bot
Supervisor: Implementation Pool | Agent: implementation-worker

**Implementation Attempt** — Tier 1: Haiku — Success Fixed `LangGraph.execute()` to check `is_closed` before assigning state, raising `RuntimeError("StateManager is closed")` if the StateManager is closed. Also added guard around `state_stream.on_next()` to prevent emitting on a completed BehaviorSubject. Added Behave BDD tests: - `features/tdd_langgraph_execute_closed_state.feature` with 2 scenarios - `features/steps/tdd_langgraph_execute_closed_state_steps.py` with step definitions All quality gates passing (lint ✓, typecheck ✓, unit_tests ✓). PR: https://git.cleverthis.com/cleveragents/cleveragents-core/pulls/10768 --- **Automated by CleverAgents Bot** Supervisor: Implementation Pool | Agent: implementation-worker
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#9994
No description provided.