paperclipai/paperclip · error
opencode_run_attach_busy
opencode_run_attach_busy
Error message
opencode_run_attach_busy
What it means
attachRun binds a new runId to an existing driver session and resets result bookkeeping. It is a synchronous, single-tenant operation: it throws opencode_run_attach_busy if a turn is still active on the session (#activeTurnId !== null), preventing two runs from interleaving on one OpenCode session.
Source
Thrown at packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts:508
provider: input.model.split("/", 1)[0],
opencodeVersion: input.runtime.version,
},
},
{ itemId: `${input.providerSessionId}:model` },
);
}
ids() {
return {
driverSessionId: this.#providerSessionId,
providerSessionId: this.#providerSessionId,
displayId: this.#providerSessionId,
};
}
attachRun(input: { runId: string }): void {
if (this.#activeTurnId !== null)
throw new Error("opencode_run_attach_busy");
if (!input.runId) throw new Error("opencode_run_attach_invalid");
this.#runId = input.runId;
this.#result = null;
this.#resultFingerprint = null;
this.#resultCallId = null;
this.#resultTurnId = null;
this.#semanticResultTextBoundary = null;
this.#semanticResultProviderMessageId = null;
this.#lastNonTerminalToolSourceSeq = 0;
this.#completedTextPartIds.clear();
this.#completedReasoningPartIds.clear();
this.#completedTextParts.length = 0;
this.#terminalTurns.clear();
this.#sendFullContext = false;
this.#emit("run.attached", { runId: input.runId, sameSession: true });
}
events(): AsyncIterable<PrpEvent> {View on GitHub (pinned to 01ad858492)
Solutions
- Await completion of the active turn (consume the run result / stream end) before calling attachRun.
- Use a fresh driver session for the new run instead of reattaching to a busy one.
- Serialize attachRun calls through a mutex/queue per session.
- If the previous turn is wedged, abort/cancel it so #activeTurnId clears, then retry attachRun.
Example fix
// before
driver.attachRun({ runId: nextRunId }); // previous turn still active -> throws
// after
await driver.waitForTurnCompletion(); // or await previousRun.result
driver.attachRun({ runId: nextRunId }); Defensive patterns
Strategy: retry
Validate before calling
if (driver.activeTurnId !== null && driver.activeTurnId !== undefined) await driver.waitForIdle();
driver.attachRun({ runId }); Type guard
const canAttach = (d: { activeTurnId: string | null }): boolean => d.activeTurnId === null; Try / catch
try { driver.attachRun({ runId }); } catch (e) { if ((e as Error).message === 'opencode_run_attach_busy') { await backoff(); await driver.waitForIdle(); driver.attachRun({ runId }); } else throw e; } Prevention
- Always await the previous run's result/stream end before reattaching
- Never share one driver instance across concurrent runs; use one session per run
- Cancel/abort wedged turns so state clears
- Route attachRun through a per-session queue or mutex
When it happens
Trigger: Calling attachRun while the previous run's turn is still in flight (stream not drained, result not consumed), or attaching a second run concurrently to the same driver instance.
Common situations: Runner restart logic reattaching before the prior run finished, missing await on the previous run promise, retry handlers attaching immediately after a failure without awaiting turn teardown, queue code reusing one driver for parallel tasks.
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 recovery failed
- OpenCode thread is not open
- codex_run_attach_busy
- OpenCode session is not ready for tool calls
- OpenCode resumed a different session
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/059b5e420f341de9.
Report an issue: GitHub.