Skip to content

[Bug] task tool causes parent session abort — both parallel and sequential dispatch #23404

Closed as not planned

Description

@tbrandenburg

Description

The task tool reliably terminates the parent session regardless of whether it is dispatched in parallel or sequentially. The subagent runs to completion, but the parent session ends with finish=NULL and 0 output tokens — all steps following the task call are silently dropped.

This makes the task tool effectively unusable in any multi-step parent workflow (e.g. scheduled routines, orchestrators, command files).

Behavior

Parallel dispatch (task + another tool call in the same turn):

  • Parent ends immediately with 0 output tokens, finish=NULL
  • Subagent runs, parent never receives result

Sequential dispatch (single task call, nothing else in the turn):

  • Same outcome — parent ends with 0 output tokens, finish=NULL after the task tool call
  • Subagent runs to completion, result is never propagated back

In both cases the subagent session is written to the DB and contains valid output. The parent session record exists but has no assistant messages after the task dispatch.

Root Cause (suspected)

The parent session loses its session ID context when SessionPrompt.prompt() is called for the subagent — originally documented in #13910. PR #13958 addressed the error path (missing task_id on failure) but was closed without merge on 2026-03-07.

The abort on the success path suggests the issue is broader: even a successful subagent return does not reliably resume the parent session. The session context appears to be discarded after task dispatches regardless of outcome.

Impact

  • Scheduled routines / orchestrator agents cannot delegate to subagents — all steps after the first task call are silently skipped
  • Multi-step command files with @agentname delegation break after the first delegation
  • No error surfaced — the parent session ends silently, making debugging extremely difficult
  • Workaround: inline all logic into the parent session, no subagent delegation at all — this defeats the purpose of the task tool entirely

Reproduction

  1. Create a command file with two sequential steps:
    • Step 1: dispatch a subagent via task tool (any prompt)
    • Step 2: any follow-up action (write a file, send a message, etc.)
  2. Run the command: opencode run --command <name>
  3. Observe: subagent in Step 1 completes, Step 2 is never executed
  4. Check DB: SELECT finish, COUNT(*) FROM message WHERE session_id = '<parent_id>' — parent has 0 output tokens after the task dispatch

Environment

Related

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Apr 19, 2026
  2. github-actions commented on Apr 19, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If your issue is distinct (e.g. the parent session ends with finish=NULL and 0 output tokens rather than hanging indefinitely), please clarify the difference so these can remain separate. Otherwise, consider adding your findings to one of the above.

  3. tbrandenburg commented on Apr 19, 2026

    @tbrandenburg
    Author

    Thanks for the cross-references. Having looked at both issues, I believe this report is distinct — here is the difference:

    #21250 and #23296 both describe a hang: the parent session stays in Thinking state indefinitely, waiting for a child task that never resolves. The parent process is still alive but blocked.

    This issue describes a silent exit: the parent session does not hang — it terminates cleanly and immediately after the task tool call. The session record in the DB shows finish=NULL with 0 output tokens on the parent, while the subagent session completes successfully and has normal output. There is no blocking, no stall, no timeout — the parent just stops existing.

    The failure modes are:

    From a root-cause perspective, #21250's completed=null empty assistant run is a different codepath than what we see here. In our case the child session is fine — it's the parent session resumption after SessionPrompt.prompt() returns that never happens.

    The DB signature that distinguishes this case:

    -- Parent session: terminates, no output after task dispatch
    SELECT finish, SUM(json_extract(data, '$.tokens.output')) as out_tokens
    FROM message WHERE session_id = '<parent_id>'
    -- result: finish=NULL, out_tokens=0
    
    -- Child session: completed normally  
    SELECT finish, SUM(json_extract(data, '$.tokens.output')) as out_tokens
    FROM message WHERE session_id = '<child_id>'
    -- result: finish='end-turn' or similar, out_tokens > 0

    Happy to keep this separate or merge into whichever issue the maintainers think best fits — but the symptom and likely fix location differ from the hang cases.

  4. tbrandenburg commented on Apr 19, 2026

    @tbrandenburg
    Author

    Verdict

    Confirmed and reproduced via code analysis. The bug is real and affects all task tool dispatches regardless of parallel vs sequential.

    Root Cause

    The core problem is a race between the Effect runLoop and the AI SDK's tool execution pipeline.

    Here's what happens:

    1. Parent session runs runLoop, LLM responds with a task tool call, stream ends with finish_reason=tool_calls
    2. handle.process() returns "continue" to runLoop — the loop immediately advances to the next iteration
    3. Meanwhile, the AI SDK is still running the task tool's execute() async function in the background, waiting for the subagent to complete (could take minutes)
    4. runLoop hits the next iteration, sees finish="tool-calls", tries to build the next LLM request — but the task tool result hasn't been written to message history yet (the subagent hasn't finished)
    5. The LLM gets called with an incomplete message history (no tool result), returns a stop, the loop exits
    6. Parent session ends with finish=NULL, 0 output tokens — done, silently

    The fundamental mismatch: runLoop only waits for the LLM stream to end, not for tool promises to resolve. Every other tool is fast enough that this race doesn't matter in practice. task is the only tool that re-enters SessionPrompt.prompt() and blocks for a long time, making the race reliably loseable.

    The fix needs to ensure runLoop doesn't advance to the next LLM call until all tool promises — including task's — have resolved and their results are committed to message history. Most likely fix location: packages/opencode/src/session/processor.ts, where handle.process() signals completion back to the loop.

  5. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  6. iatsiuk commented on May 4, 2026

    @iatsiuk

    cannot reproduce on current dev (HEAD 2c819f2, ai@6.0.168). added a mock-driven regression test that drives parent.loop with a task tool call (and a 2-sequential variant) - both pass.

    ai sdk 6.x awaits every tool execute() promise before emitting finish-step, so Stream.runDrain in processor.ts:683 only returns after every tool-result has been handled by completeToolCall. the race described in the root-cause comment is not reachable through that contract (landed in #18433, 2026-03-27).

  7. github-actions commented on Jul 5, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions