Repository navigation
fix: restore throw-based abort for /acp subcommands (#398) - #399
Conversation
…398) PR #297 (fixing #296's ERROR-log noise) downgraded two throw statements in createCommandExecuteHandler to normal returns. opencode's Plugin.trigger only aborts a command on hook errors, and the acp command is registered with template: "" (no $ARGUMENTS), so a normal return lets opencode append the raw arguments to the empty template and send them to the model as a user message. Since v1.17.0 this leaked "status"/"stats"/"context" as plain user messages and triggered spurious ~40K-token model calls (#398). Restore the sentinel throw in both spots and add a regression-guard test suite that asserts every handled branch rejects with __DCP_CONTEXT_HANDLED__ and that the notification prompt carries noReply:true + ignored:true. The guard was verified to fail against the buggy code (mutation check). Known accepted cost: one level=ERROR log line per /acp invocation on opencode >= 1.18.18 (#296). Follow-up: negotiate a silent-abort API upstream.
…sion abort assertion (#398)
📦 Built Plugin ArtifactBranch: Option A — Install from npm PR tag (recommended)opencode plugin opencode-acp@pr-399 --globalEach push to this PR publishes a new version under the Option B — Install from GitHubopencode plugin "github:ranxianglei/opencode-acp#2026-09-15_restore-command-abort-throw" --globalOption C — Download artifact
tar xzf opencode-acp-pr399.tgz
cp -r package/dist ~/.cache/opencode/packages/opencode-acp@latest/node_modules/opencode-acp/dist
This comment is automatically updated on each push. |
[bot] 🏷 Verification report for PR #399 (fix for #398) — this is completed agent work, so per protocol I pulled the branch and verified instead of redoing it. What I verified (all independently, not just trusting the description):
Assessment: root cause analysis is sound (I confirmed the mechanism against opencode source myself), the fix restores ≤v1.16.0 semantics with the accepted cost of one ERROR log line per Ready for human merge once 中文摘要:独立验证了该修复——恢复 |
Problem
Since v1.17.0 (regression commit
5fd8f5ee9, PR #297), running/acp status,/acp stats, or/acp contextleaked the subcommand word into the model as a plain user message, triggering a spurious model call (~40K input tokens in the reporter's A/B test). Bare/acp,/acp help, and/acp exportwere unaffected.Root cause chain (verified against opencode 1.18.x source)
Plugin.triggeraborts acommand.execute.beforehook only when the hook throws (packages/opencode/src/plugin/index.ts:284-296); the hook output type has only{ parts }— there is no cancel signal (@opencode-ai/pluginCommandOutput).acpcommand withtemplate: ""(no$ARGUMENTS) (index.ts:194-197); for placeholder-less templates opencode appends the raw arguments to the template (packages/opencode/src/session/prompt.ts:1390-1393) and sends the result to the model.throw new Error("__DCP_CONTEXT_HANDLED__")to a barereturn, and removed the trailing throw after the context fallback — so the command was no longer aborted and the argument word reached the model. Its replacement test asserted the wrong property (handler resolves) instead of the observable contract (command aborted).Fix
lib/hooks.tscreateCommandExecuteHandler: restoredthrow new Error("__DCP_CONTEXT_HANDLED__")after each handled branch (stats/status/bare, context fallback) — back to ≤v1.16.0 semantics. Added a[FIX #398]comment documenting that throwing is the only available abort mechanism and must not be converted back to a return.level=ERRORline per/acpinvocation (the log noise [Bug]: Error: __DCP_CONTEXT_HANDLED__ #296 complained about). A spurious ~40K-token model call is far worse than one log line. Follow-up: negotiate a silent-abort API upstream (hook output control field / explicit cancel signal); tracked in devlog §7.Tests
tests/hooks-permission.test.ts: replaced the flawed [Bug]: Error: __DCP_CONTEXT_HANDLED__ #296 test with a regression guard: every handled branch (stats,status,"",context,help,export --stdout) must reject with the sentinel and record exactly onesession.promptwithnoReply: true+ignored: true(notification path intact, no plain-message leak). Non-acp pass-through preserved. Legacy deny-permission test tightened to the same abort assertion.lib/hooks.tsmakes the guard fail (not ok 4 - command execute aborts every handled /acp branch by throwing (regression guard #398)); restored → green. This confirms the test catches the original bug.npm run typecheckPASS; full suite 1264/1264 PASS.Review
Dual-agent review per AGENTS.md §5.3 (code) + §5.6 (tests): both reviewers returned APPROVE-WITH-NITS, no blockers/majors; all actionable nits addressed in follow-up commit
2202bcc.Devlog:
devlog/2026-09-15_restore-command-abort-throw/Fixes #398