Files
PaperClipAI/packages
DottaandClaude Opus 5.5 86e9c9354d test(opencode): pin Stop during a pending MCP tool call (#19550)
## 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>
2026-11-06 13:23:24 -06:00
..