Repository navigation
fix(opencode): agent goes idle after a v2 question is dismissed - #6177
Conversation
OpenCode ends the execution when a question is dismissed but reports the interruption with the reason "shutdown", which the turn tracker keeps open waiting for a resume that never comes. The agent stayed running, and Stop, reload and send were refused. A question dismissed from Paseo now lets that interruption end the turn as canceled.
|
There was a problem hiding this comment.
Needs from you: nothing, merged
Product delta: Before, dismissing an OpenCode v2 question from Paseo (question card or paseo permit deny) left the agent running for good, and Stop, reload, and send were refused until the daemon restarted. After, the agent goes idle and the next message runs.
Protocol delta: none
This pull request ends the OpenCode v2 turn as canceled when Paseo itself dismisses a question in the agent's own session. Every other shutdown interruption still keeps the turn open for a resume.
Review
- Does it fix what was reported? Yes. The diagnosis observed OpenCode sending
form.cancelledand thensession.execution.interruptedwithreason: "shutdown".SessionTurns.reconcileTurn(turns.ts) returns early on that reason, so no terminal turn event is emitted andinterrupt()waits on a completion that never settles. That is the reporter's stuckrunningstate and the Stop error. The regression test drivesrespondToPermission(deny)through the session and checks forturn_canceledfollowed by a completed next turn. It does not depend on how the fix is implemented. - Is it the right fix? Yes, within its scope. The early return for
shutdownis intended (#5674, whose tests still pass). OpenCode gives a dismissed question the same reason, so the adapter has to use what Paseo itself knows: it dismissed the question. The flag lives on the turn inSessionTurns, the layer that owns the turn outcome, and only changes what ashutdownmeans for that turn. Possible reshaping for later: denied tool permissions get stuck the same way (see #6184). If that turns out to be the sameshutdowninterruption,questionDismissedcould be generalised to "Paseo rejected a request in this turn". This PR does not need that change. - Does it do anything else? No. The change touches only the OpenCode v2 adapter (
turns.ts,session.ts,permissions.ts) and a test. There is no protocol, schema, setting, dependency, or design change. The diagnose report's notes (dismissals made outside Paseo, child-session questions, and steer-cleared questions keep the old wait; the broader Stop recovery in #3749) are follow-ups and none blocks this merge. - Is it worth shipping? Yes. Any OpenCode v2 user who dismisses a question hits it every time, and the only recovery is restarting the daemon, which interrupts every other agent. The fix is 18 lines in one provider adapter.
QA checklist (real OpenCode 2.0.20 through an isolated daemon built from this branch, with the web app where noted)
- Reported scenario: pass. On the PR head,
paseo permit denyon a real question left the agentidle. Stop returned without error, reload succeeded, and a follow-uppaseo sendcompleted. On the merge base, the same deny left the agentrunningafter 11 s, and Stop printedactive run cancellation was not acknowledged. - Regression test: pass. "ends the turn when the user dismisses a question" failed with the three source files at the merge base (no terminal event) and passed on the head. The full OpenCode provider suite passed (15 files, 160 tests), and typecheck and lint were clean.
- Answering a question: pass. Selecting an option in the app and submitting it continued the turn with the answer, and the turn ended idle.
- Stop on a normal turn: pass. Stopping a running turn with no question made the agent idle, and the next message completed.
- Dismiss from the web app's question card: pass. The card closed, and a message sent from the composer ran to completion (before and after screenshots taken).
- Denying a non-question tool permission: same behavior on head and merge base, so this PR does not change it. In both, the agent stayed
runningand Stop was refused. That is a separate existing bug, filed as #6184.
Outside the checklist
- Denied tool permission leaves the agent stuck: real, shown on
main, and not caused by this PR. Filed as #6184. - One OpenRouter model called a tool name OpenCode does not have, so the QA run switched models to get a real question. This is model behavior, not counted.
- Lint first picked up the QA run's temporary daemon files and was clean once they were removed. This came from the test environment, not counted.
Review threads: the one thread (follow-up turn untested) was answered in 3be3cbd and is resolved. The test now covers the next message.
Decision: merged as a squash. Closes #6136.
Reviewed commit: 3be3cbd
## Summary Bumps the `paseo` acq kit from Paseo 0.10.2 to 0.12.0: `PASEO_CLI_VERSION` in `spec.yaml` and the fallback default in `paseo-start.sh`. 0.11.0 and 0.12.0 fix OpenCode v2 under Paseo, which matters now that the `opencode` kit installs v2 in every OpenCode sandbox: agents stuck after a dismissed question or denied permission (getpaseo/paseo#6177, #6188), multi-select answers (#6149), daemon OOM on long v2 streams (#6262), and HTTP 500s instead of Paseo tool errors (#6519, #7183). 0.11.0 also fixes `paseo daemon status` reporting a running daemon as stopped after the host wakes from sleep (#6279). The daemon launch surface the kit uses (`paseo daemon run`, `paseo daemon restart`, the `config.json` settings) is unchanged. The release notes' breaking changes don't touch the kit: `paseo plugin add owner/repo` installs from the registry (0.11), `paseo run --background` is deprecated in favor of `--no-wait`, and `paseo permit allow|deny` exit non-zero when the daemon doesn't confirm (0.12). ## Verification - Paseo node tests (16/16) and `validate-kits.py --strict` pass. - Live `RUN_ACQ=1` kit verify (msb 0.7.8, acq 4.0.2): all checks pass. - Downstream, with a team kit and the `opencode` kit at 2.0.30 on `devenv-shell:1.0.0`: Paseo 0.12.0 ran an OpenCode 2.0.30 agent with the team config, and the session survived a backup, a wiped `opencode.db`, and a restore, then answered a follow-up. - Seen once: on first `acq run`, the wrapper's `paseo daemon restart --timeout 30` reported failure and the kit fell back to the supervisor respawn, which worked; the daemon log shows the restart went through. A manual `paseo daemon restart` afterwards succeeded in under 4 s. ## Eligibility & Accountability - [x] I am a **federal employee or contractor** (PRs merge with `@GSA-TTS/agentic-coding-team` code-owner approval; eligibility is attested here, not authenticated). - [x] **I understand this change and can explain it in my own words** — I am the author of record and accountable for it. - [x] By opening this PR, I **attest I have the right to release this work** under the repository's CC0-1.0 dedication. ## AI Assistance Disclosure - [x] AI tools were used in this contribution. - [x] I verified all AI-generated content.
Linked issue
Closes #6136
Refs #3749 (the same Stop error on other providers, with a different cause)
Type of change
Reasoning
Dismissing an OpenCode v2 question from Paseo, from the question card or with
paseo permit deny, left the agent atrunningfor good. Stop, reload, and sending a message were then refused withactive run cancellation was not acknowledged, and only a daemon restart freed it. With this change the turn ends as canceled when the question is dismissed, and the agent is idle and accepts the next message.OpenCode ends the execution when its question is dismissed, but reports the interruption with the reason
shutdown. Paseo deliberately keeps a turn open on ashutdowninterruption, because OpenCode uses the same reason for an execution it will resume (#5674 and its tests). Nothing resumes after a dismissal, so the turn never closed. Paseo knows when it dismissed the question itself, so that interruption now ends the turn, and every othershutdowninterruption keeps the existing wait.Goals
shutdowninterruption that Paseo did not cause still keeps the turn open for a resume, as before.Non-goals
QA
Real OpenCode v2.0.20 through the daemon (
OpenCodeRuntimeClientselecting v2), model through OpenRouter, in a temporary real-provider test that is not part of this PR:paseo permit denydoes (agent_permission_responsewithbehavior: "deny").main): the permission resolved, but the agent stayedrunningfor 30s. Instrumented logs showed OpenCode emittingform.cancelled, thensession.execution.interruptedwithreason: "shutdown", andsession.activereturning{}on every later reconcile.idle, and a follow-up message ran to completion with the agent back atidle.Regression test:
packages/server/src/server/agent/providers/opencode/v2/agent.test.ts, "ends the turn when the user dismisses a question". It checks the turn is canceled and that the next turn completes. It fails onmain(no terminal turn event) and passes with this change.Risk surface: the OpenCode v2 adapter only (
turns.ts,session.ts,permissions.ts). The existing shutdown-and-resume tests pass unchanged.Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatpasses