Repository navigation
Conversation
Declining a tool call interrupts the runner fiber with no reason, which the settlement path read as "shutdown": the declined turn kept its claim, skipped its job cancellation, and resumed after a server restart. The drain now names its own stop through the coordinator before interrupting, so a decline settles as a deliberate user stop and releases the turn.
|
The following comment was made by an LLM, it may be inaccurate: |
There was a problem hiding this comment.
The fix works. I ran the #54180 reproduction from source with the simulated model: on the base, rejecting the permission and restarting the server sends a second model request with "The server restarted while you were working". With this PR there is no further request after the restart. session-execution and session-runner tests pass (259/0) and packages/core typecheck is clean.
Public API: unchanged. Coordinator.stopReason and the reportDeclined drain input are internal to core. The only visible difference is that session.execution.interrupted now reports reason: "user" instead of "shutdown" for a declined tool call, which is an existing value, and the claim is released and session jobs are cancelled the same way as for a user interrupt.
No blocking concerns. One design note for maintainers: the stop reason is passed as a callback through four layers (execution → runner → llm → step) right before a bare Effect.interrupt. Having the step return a typed declined result that execution maps to { type: "interrupted", reason: "user" } would keep the decision visible in the return type rather than in a side channel. That's a preference, not a defect. The two added tests are small and focused.
Related: #36347
|
Closing this to stay within GitHub's 3-open-pull-request limit for contributors without write access, which currently blocks every new pull request here. The branch remains on my fork, so I can reopen it on request. |
A declined permission or a dismissed question ends the turn with a bare self-interrupt, which the settle reads as a shutdown. The turn then keeps its execution claim, is reported as `session.execution.interrupted` with reason `shutdown`, projects no idle outcome, and resumes at the next server start with "The server restarted while you were working…", so the model goes on with the work the user declined. Clients that wait out a shutdown (opencode.el) also keep the session busy after the reject. The step now names the stop before interrupting: the drain input gains `reportDeclined`, which the execution layer wires to the coordinator's new `stopReason(key, "user")`. The settle releases the claim and cancels the session's jobs as for any user interrupt. Teardown is unchanged: a location that shuts down is closed before its pending asks are failed, and the step completes those for continuation before it reaches the decline. This follows upstream PR anomalyco#54344 (closed unmerged) for upstream issue anomalyco#54180.
Issue for this PR
Closes #54180
Type of change
What does this PR do?
Declining a tool call settles the turn through a bare
Effect.interruptwith no reason recorded, so the settle path reads it as a"shutdown"interrupt. The claim therefore survives the drain, and the declined turn resumes after a server restart.The fix gives that interrupt a name so the settle path can tell a deliberate user stop from a shutdown:
CoordinatorgainsstopReason(key, reason)(packages/core/src/session/run-coordinator.ts), which writesexecution.interruptionReason.reportDeclined(packages/core/src/session/runner/index.ts).packages/core/src/session/runner/step.tscallsinput.reportDeclined()before its bareEffect.interrupt, andrunner/llm.tsthreads the callback from the drain input into the step.packages/core/src/session/execution.tswires it tocoordinator.stopReason(sessionID, "user").The settle path already does the right thing once the reason is not
"shutdown"—jobs.cancelruns and the claim is released — so the declined turn no longer resurrects.How did you verify your code works?
packages/core/test/session-execution.test.ts(settles a declined tool call as a deliberate stop that never resurrects) asserts theExecution.Interruptedreason is["user"], that the claim is released, and thatrestart.resumeSuspendedSessionsfinds nothing to resume. A runner-level assertion inpackages/core/test/session-runner.test.tscovers the step naming the stop before it interrupts.tsgo -b tsconfig.json tsconfig.tests.jsonexits 0;oxlintreports 0 problems.packages/coresuite shows two more failures than a stashed baseline, and both are the known load flakes (PluginSupervisor reload,subagent observer lifetime): re-running them standalone fails on both sides, in different combinations.Checklist
Added by the opencode bot (AI-generated).
Related: #36347