paperclipai/paperclip · error
stale OpenCode turn
Error message
stale OpenCode turn
What it means
`interrupt` aborts the OpenCode session via `/session/{id}/abort`. If the caller passes a `turnId` that does not match the currently active turn id tracked by the driver, the driver treats the request as targeting a turn that no longer exists and throws instead of aborting, protecting the live turn from being cancelled by stale bookkeeping.
Source
Thrown at packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts:604
tools: { question: true },
...(this.#sendFullContext
? { system: this.#systemInstructions }
: {}),
parts: [{ type: "text", text: prompt }],
}),
},
);
this.#sendFullContext = false;
return { turnId };
}
async interrupt(input: { turnId?: string; reason?: string }): Promise<void> {
if (
input.turnId &&
this.#activeTurnId &&
input.turnId !== this.#activeTurnId
)
throw new Error("stale OpenCode turn");
await api(
this.#fetch,
this.#runtime,
`/session/${encodeURIComponent(this.#providerSessionId)}/abort`,
{ method: "POST" },
);
}
pendingRuntimeRequests(): HarnessRuntimeRequest[] {
return [...this.#pendingRuntimeRequests.values()].map(({ request }) =>
structuredClone(request),
);
}
async resolveRuntimeRequest(input: {
requestId: string;
turnId: string;
resolution: HarnessRuntimeRequestResolution;View on GitHub (pinned to 01ad858492)
Solutions
- Fetch the current active turn id via `await session.snapshot()` (`activeTurnId` field) and pass that to `interrupt`.
- If you intend to abort regardless of turn bookkeeping, call `interrupt({})` without a `turnId` — the guard is skipped.
- If the turn already ended, no interrupt is needed; drop the stale turn id and skip the abort call.
- After restoring a session from a persisted snapshot, resynchronize turn ids from live events before issuing turn-scoped calls.
Example fix
// before
await session.interrupt({ turnId: staleTurnId }); // throws 'stale OpenCode turn'
// after
const snap = await session.snapshot();
if (snap.activeTurnId) await session.interrupt({ turnId: snap.activeTurnId });
else await session.interrupt({}); Defensive patterns
Strategy: validation
Validate before calling
const snap = await session.snapshot(); const isValid = !input.turnId || input.turnId === snap.activeTurnId; if (!isValid) return; // nothing to interrupt
Type guard
function isCurrentTurn(snap, turnId) { return turnId === undefined || turnId === snap.activeTurnId; } Try / catch
try {
await session.interrupt({ turnId });
} catch (e) {
if (e.message === 'stale OpenCode turn') {
const snap = await session.snapshot();
if (snap.activeTurnId) await session.interrupt({ turnId: snap.activeTurnId });
} else throw e;
} Prevention
- Read turn ids from `snapshot()` or live events, never from cache.
- Skip interrupts for turns already known to be terminal.
- After restoring sessions, resynchronize turn state before scoped calls.
- Omit turnId when you intend an unconditional abort.
When it happens
Trigger: Calling `session.interrupt({ turnId })` with a turnId from an earlier, already-finished turn while a newer turn is active (`input.turnId !== this.#activeTurnId`). Happens when cached turn ids are reused after reconnect, or when a persisted snapshot's `activeTurnId` is out of date relative to the live session.
Common situations: A retry worker holds a stale turn id after the original turn already completed; two processes restored the same session from `snapshot()` and hold different views of the active turn; UI shows an old run whose turn id was superseded and the user clicks 'Stop'.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- OpenCode session already has an active turn
- OpenCode request ${input.requestId} belongs to a stale turn
- OpenCode evals require exact version 1.18.17; received ${ver
- [opencode-local] Remote model availability probe for "${mode
- [opencode-local] Remote `opencode models` returned no models
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/168015259281a27b.
Report an issue: GitHub.