paperclipai/paperclip · error
OpenCode request ${input.requestId} belongs to a stale turn
Error message
OpenCode request ${input.requestId} belongs to a stale turn What it means
After finding the pending request, `resolveRuntimeRequest` verifies the caller's `turnId` matches both the turn the request was raised in (`pending.request.turnId`) and the session's currently active turn (`#activeTurnId`). A mismatch means the resolution belongs to an earlier, finished turn and is refused so answers cannot leak across turn boundaries.
Source
Thrown at packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts:633
structuredClone(request),
);
}
async resolveRuntimeRequest(input: {
requestId: string;
turnId: string;
resolution: HarnessRuntimeRequestResolution;
}): Promise<void> {
const pending = this.#pendingRuntimeRequests.get(input.requestId);
if (!pending)
throw new Error(
`OpenCode request ${input.requestId} is no longer pending`,
);
if (
pending.request.turnId !== input.turnId ||
this.#activeTurnId !== input.turnId
) {
throw new Error(
`OpenCode request ${input.requestId} belongs to a stale turn`,
);
}
const resolution = parseHarnessRuntimeRequestResolution(
pending.request.requestKind,
input.resolution,
pending.request.input,
);
if (pending.settling)
throw new Error(
`OpenCode request ${input.requestId} is already settling`,
);
pending.settling = true;
const submit = async (operation: Promise<unknown>) => {
try {
await operation;
} catch (error) {
if (this.#pendingRuntimeRequests.get(input.requestId) === pending)View on GitHub (pinned to 01ad858492)
Solutions
- Read the authoritative turn id from `session.pendingRuntimeRequests()` (`request.turnId`) and pass exactly that value.
- Confirm via `await session.snapshot()` that `activeTurnId` still equals the request's turn; if not, abandon the resolution — the request is expired.
- On receiving this error, drop the pending answer and surface the request as expired rather than retrying with the same stale turnId.
- Ensure the caller's turn id comes from the same event stream instance (`events()`) that raised the request, not a different session handle.
Example fix
// before
await session.resolveRuntimeRequest({ requestId, turnId: cachedTurnId, resolution });
// after
const pending = session.pendingRuntimeRequests().find(r => r.requestId === requestId);
await session.resolveRuntimeRequest({ requestId, turnId: pending.turnId, resolution }); Defensive patterns
Strategy: validation
Validate before calling
const pending = session.pendingRuntimeRequests().find(r => r.requestId === requestId); if (!pending || pending.turnId !== turnId) throw new StaleTurnError(requestId);
Type guard
function turnMatches(session, requestId, turnId) {
const p = session.pendingRuntimeRequests().find(r => r.requestId === requestId);
return !!p && p.turnId === turnId;
} Try / catch
try {
await session.resolveRuntimeRequest({ requestId, turnId, resolution });
} catch (e) {
if (e.message.includes('belongs to a stale turn')) {
// abandon the answer; surface the request as expired
} else throw e;
} Prevention
- Take turnId from the same event stream that raised the request.
- Verify `snapshot().activeTurnId` still matches before resolving.
- Do not replay stored (requestId, turnId) pairs across turn boundaries.
- Expire queued answers when the owning turn completes.
When it happens
Trigger: Calling `resolveRuntimeRequest` with a `turnId` different from `pending.request.turnId`, or after the active turn advanced past the one owning the request (old turn completed, new turn started, request map not yet cleaned).
Common situations: A caller caches `(requestId, turnId)` pairs and replays them after a turn restart; turn id captured from a stale event before a reconnect re-minted the active turn; mixing requests between two restored session instances.
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
- stale OpenCode turn
- OpenCode request ${input.requestId} is no longer pending
- OpenCode request ${input.requestId} is already settling
- OpenCode evals require exact version 1.18.17; received ${ver
- [opencode-local] Remote model availability probe for "${mode
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/941f030fa78bab50.
Report an issue: GitHub.