Skip to content

fix(core): keep subagent job across interrupt with resume - #54660

Closed
heliowap wants to merge 1 commit into
anomalyco:devfrom
heliowap:subagent-resume-handoff
Closed

heliowap wants to merge 1 commit into
anomalyco:devfrom
heliowap:subagent-resume-handoff

Conversation

@heliowap

@heliowap heliowap commented Oct 12, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #54658

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

When an interrupt with resume=true continues a background subagent, the parent now receives state="completed" with the subagent's reply. Before this fix, the parent received state="cancelled".

Two parts of packages/core caused the bug:

  • The settled hook in SessionExecution cancels the session's job after every user interrupt.
  • SessionRunCoordinator.run returns the exit of the interrupted execution, even when a successor execution runs the steer prompt. The job takes that exit and records cancelled.

The fix adds SessionRunCoordinator.handoff. If the next inbox item is a steer prompt, SessionExecution.interrupt calls handoff instead of wake. handoff marks the interrupted execution as continued. The settled hook then skips jobs.cancel, and run returns the exit of the successor.

This also changes what other callers of run get. A foreground subagent job, for example, now waits for the reply to the steer prompt and completes with it, instead of ending as cancelled at the interrupt.

Interrupts without resume work as before. If only a compaction or a move waits, interrupt still calls wake, and the job stays cancelled. With handoff in that case, the parent would get an old reply marked as completed.

Known gap: SessionExecution.interrupt reads the inbox after the interrupt starts. If cleanup of the interrupted execution finishes before that read, the parent still gets cancelled. Cleanup stops the running tool and writes events, so it almost always takes longer than the read. Closing the gap needs a change to interrupt in the coordinator, so I left it out of this PR.

How did you verify your code works?

  • I added a test to packages/core/test/session-execution.test.ts: "a background child job follows an interrupt with continue through the resumed steer". It fails on dev with cancelled and passes with this fix.
  • I added a second test to the same file: "a background child job stays cancelled when continue only resumes control work".
  • In packages/core, I ran bun test on the session, job, and subagent tests, and bun typecheck. At the root, I ran bun run check.
  • I started opencode serve from this branch and ran the steps from the issue. With a steer prompt in the inbox, the parent received state="completed" and the reply. Without one, the parent received state="cancelled".

Screenshots / recordings

n/a

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #50751

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reproduced the problem on the base and it is gone with this change. I ran the #54658 opencode-drive script with a simulated model against both checkouts. On the base, the parent gets only cancelled while the child replies DONE. With this PR, the parent gets one completed notification containing the reply. The core session, coordinator, job and subagent tests pass, and packages/core typechecks. The new steer test fails on the base as expected.

Public API: unchanged. SessionRunCoordinator.handoff and the extra continued argument to settled are internal to packages/core, and settled has a single implementer.

The approach looks sound. join follows the successor only when the interrupted execution was handed off and its exit is interrupt-only, so other interrupts and failures behave as before. Callers that join through run (for example a foreground subagent) now also wait for the resumed steer's result, which looks like the right behaviour. The remaining race you describe, where cleanup settles before handoff runs, still yields cancelled. That is acceptable as a known gap for this PR.

Related: #50751

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subagent: parent gets cancelled when an interrupt with resume=true continues a background subagent

1 participant