Repository navigation
Conversation
BlueX888
force-pushed
the
fix/agent-close-hang-update-agent-race
branch
from
October 26, 2026 08:52
eace453 to
5b9bc63
Compare
An AgentTask awaited from a function tool unwinds on the tool task itself. When update_agent() replaced the agent in the meantime, the handoff branch awaited old_activity.aclose(), whose tool executor drain waits for that same non-cancellable tool task, so the task awaited itself and never signalled inactive. Session close then hung in _wait_for_inactive() and never emitted close; if the task completed instead, the tool stayed blocked forever. Close the superseded activity in a detached task when the session is not closing, and let the close path own it during a close. The close path now also drains/closes the AgentTask's own activity when update_agent() skipped its handoff, and drain() is a no-op on an already closed activity. Track which agent activity owns the session-scoped keyterm detector: a detached close that lands after the replacement activity bound the detector is stale and must not disable detection for the activity that now owns it. Add regression tests for both the close-time hang and the tool that never returns after the task completes, plus one for the keyterm detector staying bound across the detached close. Co-Authored-By: Claude Code <noreply@anthropic.com>
A tool that awaits a second AgentTask after update_agent() superseded the first handed the tool's activity to a detached close. The second task then resumed that activity, whose lock the detached close holds while draining the very tool task doing the resume, so task, tool and session close all hung. Mark the activity as owned by the detached close, refuse to start another inline task on it, and never resume a closed or detaching activity. A superseded AgentTask that unwinds after session.aclose() found the close path already finished: it only walks from the session's current activity, which update_agent() replaced, so the old activity was never closed (its tools and MCP toolsets leaked). Track whether the in-flight close will reach the old activity and close it from a detached task when it won't. Add regression tests for the two-task deadlock and the late-unwind leak. Co-Authored-By: Claude Code <noreply@anthropic.com>
BlueX888
force-pushed
the
fix/agent-close-hang-update-agent-race
branch
from
October 29, 2026 23:13
5b9bc63 to
f0ca0fb
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A function tool awaits an
AgentTask. While it waits,update_agent()replaces the agent. When the task then completes, it unwinds on the tool task itself and the superseded-activity branch awaitsold_activity.aclose()— whose tool-executor drain waits for that same non-cancellable tool task. The task awaits itself: the tool never returns,session.aclose()hangs in_wait_for_inactive, nocloseevent is emitted, and teardown can hitRecursionError.Reproduced on
049aea8; the new regression tests fail there (timeout in_wait_for_inactivewithfut_done=True,inactive_ev=False, tool task non-cancellable; the tool-never-returns and late-unwind cases also time out).Two adjacent patterns came out of review and are covered by this revision:
AgentTaskafter the first was superseded: the detached close holds the activity's lock that the second task's handoff needs, so task, tool and close all hang;session.aclose()returned: the close path had already walked past the superseded activity, which then leaked (tools/MCP toolsets never closed).Fix
_close_detached_activity()closes a superseded activity from a detached task instead of awaiting it inside the tool task. During a session close, the close path keeps ownership;_will_close_activity()checks whether the teardown walk actually reaches the activity and the detached close is used when it does not.AgentTaskon it is refused withToolError— the same contract a tool already sees when the session is closing.drain()/aclose()are idempotent on an already closed activity.Tests
tests/test_agent_task_close_race.pygains:test_aclose_after_update_agent_while_tool_awaits_agent_task— the original close-time hangtest_agent_task_completion_after_update_agent_returns_to_tool— the tool that never returned after the task completedtest_second_agent_task_in_tool_after_update_agent_does_not_deadlock— the two-task cycletest_late_agent_task_unwind_after_aclose_closes_superseded_activity— the late-unwind leaktest_detached_close_keeps_keyterm_detection_on_replacement— the keyterm ownershipReceipts on
049aea8: the new close-race tests fail/hang for the reasons above. With this branch:tests/test_agent_task_close_race.py14 passed (55+ reruns), agent-session + task suites 260 passed, full unit suite 6035 passed with the same 10 pre-existing failures / 10 environment errors as049aea8;ruff check/ruff format --checkclean. Merge-tree is clean against #8147, #7956, #7930, #7940, #7957 and current main.Residual (theoretical): nested
AgentTaskchains racingupdate_agent()could still miss a close; no reachable case was constructed.🤖 Generated with Claude Code