mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 16:02:36 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The native runner runs OpenCode agents and settles their cost after each run > - An operator can stop a run while the agent waits inside a Paperclip MCP tool call > - Before #18610, OpenCode then kept calling the model after the Stop. The turn never closed, so the run cost never settled > - #18610 fixed the cause, but no test covers a Stop during a pending MCP tool call > - This pull request adds that regression test at the driver boundary > - The benefit is that a change to empty-answer recovery or interruption cannot bring back runaway model work after a Stop ## Linked Issues or Issue Description Refs #18610 (the fix that this test pins) Refs #19497 (accounting that waits on a terminal run receipt) **What happened?** A board operator stopped a native OpenCode run. The agent was blocked inside a gated `paperclip_call_assigned_tool` call. The runner acknowledged the interrupt, but OpenCode made 8 more model requests in the next 60 seconds. No `turn.cancelled` event arrived. The run stayed at `costAccountingPending: true` and `accountingReceiptReady: false`, so cost accounting never settled. **Expected behavior** A Stop aborts the provider. The turn closes with `turn.cancelled`. The provider makes no new model requests. Cost accounting then settles from the final receipt. **Steps to reproduce** 1. Start an OpenCode turn with a dynamic Paperclip tool whose handler does not return. 2. Let the provider call that tool through the MCP bridge. 3. Fail the pending call (runnerd does this first on Stop), then interrupt the turn. 4. Let OpenCode report the abort as a bare `session.idle`. 5. Before #18610, the driver sent a corrective "give a final answer" prompt. The provider then continued work. **Root cause** The driver sent `/session/:id/abort` correctly. OpenCode 1.18.34 aborts correctly, and sometimes reports the abort only as `session.idle`. Empty-answer recovery treated that idle as an incomplete answer and sent a new prompt. #18610 added `#interruptedTurnId`, so recovery now stops for an interrupted turn. The failing eval ran on `3b66c3c144`, which is earlier than #18610. ## What Changed - Add `packages/paperclip-runner/src/drivers/opencode/interrupt-pending-tool-call.test.ts`. It calls the real MCP bridge over HTTP, holds the dynamic tool call open, and interrupts the turn. It checks three abort reports: - `idle-after-settle`: runnerd fails the call, then the interrupt, then a bare idle. - `idle-before-settle`: an idle while the call is still pending, then the late failure, then an idle. - `aborted-error`: `MessageAbortedError`. - Each case asserts one prompt only, exactly one terminal event (`turn.cancelled`), and no empty-response correction diagnostic. - No product code changes. Master already has the fix. ## Verification - `pnpm --filter @paperclipai/paperclip-runner exec vitest run src/drivers/opencode/interrupt-pending-tool-call.test.ts`: 3 passed on this branch. - The same test at `3b66c3c144` (before #18610): `idle-after-settle` and `idle-before-settle` fail with 2 prompts (the corrective prompt). `aborted-error` passes. This matches the eval symptom. - `vitest run src/drivers/opencode/`: 14 files, 185 tests passed. - `tsc -p tsconfig.test.json --noEmit` for the runner: no new errors. A pre-existing error in `src/eval/workflow-harness.ts` comes from an unbuilt local eval-kernel package. - `server` `native-stop-accounting.integration.test.ts`: 5 passed. This confirms that settlement completes after a Stop when the terminal event and final receipt arrive. - Manual, not committed: the same flow with the real OpenCode 1.18.34 binary, a mock OpenAI-compatible model, and a gated tool. I tested it through the driver alone and through runnerd, the proxy, and the driver. The interrupt produced `turn.cancelled`, and the model received no more requests. I also tried different gate-release and model-latency timings. - The new test waits for the terminal event with a bounded `expect.poll`, not a fixed delay (review feedback). - `pnpm verify` exited 2 (broader verification required) because the new test file is outside the resolved test graph. CI supplies the broad coverage. ## Risks Low risk. This change adds one test file only. The test uses the existing fake OpenCode fixture and an injected `fetch`. It uses real timers with short waits. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used - Claude Opus 5.5 (Anthropic), model ID `claude-opus-5-5`, used in Claude Code with tool use (shell, file edits, code execution) and extended reasoning. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>