Skip to content

[FEATURE]: Sanitize interrupted running/pending tool parts on subagent resume #50214

Closed as not planned

Description

@AllanSantos-DV

Verification

  • I have checked that this feature has not been suggested before. Related existing issues are referenced below.

Problem

When a background subagent (spawned via the task tool) is interrupted mid-flight — by a service crash, OOM kill, network drop, or provider hang — the tool parts remain in running or pending state in the database. On resume via task_id, the orchestrator detects these orphaned parts but has no way to distinguish a genuine crash from a user-initiated cancel.

This causes two concrete issues:

  1. Stale running parts block resume: The resume path previously used .find() which only sanitized the first running tool part, leaving others orphaned.
  2. No crash-vs-cancel distinction: Both user cancels and crashes produce the same state=error with no metadata, making it impossible for the parent to decide whether to retry.

Current behavior (from source)

  • packages/opencode/src/tool/task.ts resume path: when task_id is provided, the code fetches the last message and sanitizes tool parts. The original .find() only caught the first running part.
  • packages/schema/src/v1/session.ts: ToolStateRunning has status: "running", ToolStatePending has status: "pending" — both are incomplete states that should be resolved on resume.
  • MessageV2.toModelMessages treats both running and pending tool parts as incomplete, so orphaned parts silently break model context.

Proposed solution

In the resume path of task.ts, before re-prompting:

  1. Iterate ALL tool parts in the last message (not just the first) using for...of with continue.
  2. Sanitize both running and pending parts to state=error with:
    • metadata.interrupted = true — distinguishes crash from user cancel
    • time.end = Date.now() — ensures end >= start invariant
    • Preserve metadata from running parts (e.g. markers, debug info)
  3. Exclude interrupted errors from terminal detection: failedInLast should filter out metadata.interrupted parts so sanitized orphans don't cause false-positive failures.

Acceptance criteria

  • After a service crash, resuming a background subagent via task_id sanitizes all orphaned running and pending tool parts to error with metadata.interrupted=true.
  • The parent session receives state=error with a recovery_hint containing the task_id, allowing retry.
  • User-initiated cancels do NOT set metadata.interrupted (they use the existing cancel path).
  • All 28 unit tests in test/tool/task.test.ts pass, including new tests for pending sanitization and historical error handling.

Related issues

Implementation notes

  • Single file change: packages/opencode/src/tool/task.ts (~22 lines changed)
  • Test additions: packages/opencode/test/tool/task.test.ts (3 new tests, 28 total)
  • Validated E2E in isolated lab: kill service mid-flight → restart → resume → tool parts sanitized correctly
  • Quality gate: independent reviewer + tester passed with zero findings

Happy to open a PR if maintainers agree on the direction.

Activity

  1. AllanSantos-DV commented on Oct 24, 2026

    @AllanSantos-DV
    Author

    Re-checked on current dev (V2, c272a63). The path from this issue (tool/task.ts, MessageV2.toModelMessages) no longer exists, and the underlying problem is already handled: SessionRunner.settleStaleToolCalls (packages/core/src/session/runner/llm.ts) runs at the start of every drain and durably fails all streaming/running tool parts with an aborted "Tool execution interrupted" error (including the child sessionID for subagent). Subagent recovery after a restart goes through the same path (session/execution/restart.ts), and session-runner.test.ts covers it ("durably fails local tools left running by a prior process", "preserves a stale subagent child session ..."). So I think this can be closed as already fixed in V2. The only remaining difference is that V2 has no structured crash-vs-cancel flag (metadata.interrupted); I won't pursue that unless maintainers want it.

  2. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the detailed write-up and for offering a PR!

    This request targets the v1 codebase: packages/opencode/src/tool/task.ts, the task_id resume path and MessageV2.toModelMessages. V1 (0.x / 1.x, the opencode-ai package) now only gets fixes for critical bugs, such as data loss, security problems or crashes with no workaround, so this improvement is unlikely to land there.

    OpenCode v2 (@opencode/cli) is a separate implementation with its own subagent and restart handling. When a run is cut off, interrupted tool calls are marked as errors ("Tool execution interrupted: …", including the child session ID), and v2 has its own logic for sessions left behind by a process that died. We recommend upgrading to v2. These v2 issues are closely related, in case you want to follow them or add to them:

    If you see the same problem with background subagents on v2, or you think this is critical on v1, please reply here with details.

  3. opencodejr commented on Oct 29, 2026

    @opencodejr
    Contributor

    Closing this as not planned. The request targets the v1 code (packages/opencode/src/tool/task.ts, the task_id resume path), and v1 only gets fixes for critical bugs, so this change is unlikely to land there.

    V2 is a separate implementation with its own handling for interrupted tool calls and restarts. If background subagents leave stale or misreported tool state on v2, please open a new issue with your version and steps to reproduce. You can also follow the related v2 work in #36349, #54658 and #59566. Thanks again for the detailed write-up!

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions