-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Problem
When a session is interrupted mid-tool (e.g., abort, crash, or user interrupt) before the tool finishes executing, the tool part is left in `"pending"` state. On next session load, `processor.ts` silently ignores these pending tool parts when processing `tool-result` and `tool-error` events — because it only checks for `"running"` state, not `"pending"`. The result is a dangling `tool_use` block sent to the Anthropic API with no corresponding `tool_result`, which causes:
```
Error: tool_use block requires a corresponding tool_result block
```
Root Cause
File: `packages/opencode/src/session/processor.ts`
Line 182 (tool-result handler):
```ts
if (match.state.status === "running") {
```
Line 206 (tool-error handler):
```ts
if (match.state.status === "running") {
```
Both checks exclude `"pending"` tools — tools interrupted before execution began are never transitioned to an error state, so they remain as orphaned `tool_use` blocks in the persisted message.
Existing Partial Workaround
`message-v2.ts` lines 660–670 converts `pending`/`running` tool parts to synthetic error results during serialization for the API. This prevents the immediate crash but does not fix the root state — the tool remains `pending` in the store, so the workaround fires on every subsequent resume too.
Fix
In `processor.ts`, extend both conditions to also handle `"pending"` tools:
Line 182 — change:
```ts
if (match.state.status === "running") {
```
to:
```ts
if (match.state.status === "running" || match.state.status === "pending") {
```
When the matched tool is `"pending"` (i.e., `startTime` was never set), synthesize a safe `startTime`:
```ts
const startTime = match.state.time.start ?? Date.now()
```
Apply the same change at line 206 for the `tool-error` handler.
Acceptance Criteria
- `processor.ts` line 182: condition includes `|| match.state.status === "pending"`
- `processor.ts` line 206: condition includes `|| match.state.status === "pending"`
- `startTime` defaults to `Date.now()` when `match.state.time.start` is `undefined` (pending case)
- Existing tests pass: `bun test` in `packages/opencode`
- Type check passes: `bun run typecheck` in `packages/opencode`
- New test: session with a `pending` tool on resume produces a `tool_result` error block (not a dangling `tool_use`)
- No `@ts-ignore`, `as any`, or suppression comments
Context
- Upstream aware: issues messages.87:
tool_useids were found withouttool_resultblocks immediately anomalyco/opencode#10616, AI_APICallError: tool_use blocks found without corresponding tool_result blocks anomalyco/opencode#2720, AI_APICallError:tool_useids were found withouttool_resultblocks immediately after anomalyco/opencode#1662, Most sessions eventually gets antool_useerror anomalyco/opencode#8377, Tool use id bug anomalyco/opencode#5750, AI_APICallError: messages.3:tool_useids were found withouttool_resultblocks immediately after: toolu_01G5ipgNZWmDHuV6cvzyQpfH. Eachtool_useblock must have a correspondingtool_resultblock in the next message. anomalyco/opencode#2214 (all open) - Upstream partial fix: PR feat: add litellmProxy provider option for explicit LiteLLM compatibility anomalyco/opencode#8658 merged 2026-01-15 (the `message-v2.ts` workaround — we already have this)
- Medium risk: plugin transform hook at `prompt.ts:658` can strip synthetic `tool_result` messages — out of scope for this fix
Quality Gates
- TDD: write failing test first
- Coverage: new code covered
- Linting passes
- `bun run typecheck && bun test` green locally before push