fix(py): record subagent end_time at SubagentStop, not at conversation end#2921
Open
James Won (jwon) wants to merge 1 commit into
Open
fix(py): record subagent end_time at SubagentStop, not at conversation end#2921James Won (jwon) wants to merge 1 commit into
James Won (jwon) wants to merge 1 commit into
Conversation
…n end When multiple `Agent` (subagent) tool calls run in parallel within a session, the subagent chain runs had `end_time` stamped at conversation-termination time rather than at actual subagent completion. In trace visualizations this made sibling `general-purpose` (chain) spans visually outlast their parent `Agent` tool spans, with all siblings closing at the same instant. `subagent_stop_hook` deferred both `.end()` and `.patch()` to `clear_active_tool_runs()` so that `PostToolUse` for the parent `Agent` tool could still attach outputs to the subagent run. `.patch()` must be deferred, but `.end()` (which only records `end_time`) can run immediately. Call `subagent_run.end()` in `subagent_stop_hook` to record `end_time` at the correct moment, and drop the redundant `.end()` call from `clear_active_tool_runs()` so the stamped time is preserved through cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
When a session spawns multiple subagents in parallel (e.g. the assistant fires several
Agenttool calls in one turn), the resulting subagent chain runs are recorded withend_timeequal to conversation termination, not actual subagent completion. In trace UIs this manifests as sibling subagent chain spans visually outlasting their parentAgenttool spans — sometimes by many minutes — and all closing at the same instant.Root cause
subagent_stop_hook(python/langsmith/integrations/claude_agent_sdk/_hooks.py) intentionally defers both.end()and.patch()untilclear_active_tool_runs()runs, so thatPostToolUsefor the parentAgenttool can attachoutputsto the subagent run first.Deferring
.patch()is required (outputs aren't known yet atSubagentStop). Deferring.end()is not — it just recordsend_time. Because both were deferred together,RunTree.end_timewas set todatetime.now()insideclear_active_tool_runs(), which only runs once at the end ofreceive_response().This deferred-end behavior dates back to #2670, which introduced subagent tracing —
.end()and.patch()were paired in the same code path, but only.patch()actually needs to wait.Fix
Record
end_timeimmediately whenSubagentStopfires by callingsubagent_run.end()in the hook..patch()remains deferred untilclear_active_tool_runs()so outputs added later byPostToolUseare still flushed.clear_active_tool_runs()no longer re-calls.end()for already-ended subagents (which would overwrite the correct timestamp withnow()).Orphan paths (
SubagentStopnever fired, or no matching Agent tool) are unchanged.Testing
test_subagent_stop_and_post_tool_use_set_outputs— previously assertedend_time is NoneafterSubagentStop/PostToolUse(locking in the deferred behavior). Now assertsend_timeis set atSubagentStopand is unchanged by subsequentPostToolUseandclear_active_tool_runscalls.test_subagent_end_time_recorded_at_stop_not_conversation_end— regression test with synthetic delays between hooks, asserting the recordedend_timematches subagent stop time, not cleanup time.make format && make lint && make testspass locally (Python SDK only).