paperclipai/paperclip · error
codex_run_attach_busy
codex_run_attach_busy
Error message
codex_run_attach_busy
What it means
attachRun on CodexHarnessSession attaches an orchestration run to the session, but only when the session is quiescent. It throws "codex_run_attach_busy" when a turn start is already pending, or — when the transport does not own quiescence — when there is still an active turn or pending runtime requests. This is a guard against corrupting an in-flight turn's state by re-attaching mid-flight.
Source
Thrown at packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts:91
}
ids(): ReturnType<HarnessSession["ids"]> {
return {
driverSessionId: this.opened.threadId,
providerSessionId: this.opened.providerSessionId,
displayId: this.opened.threadId,
};
}
async attachRun(input: { runId: string }): Promise<void> {
this.assertProtocolIntegrity();
const transportOwnsQuiescence = this.transport.attachRun !== undefined;
if (
this.turnStartPending ||
(!transportOwnsQuiescence &&
(this.activeTurnId !== null || this.pendingRuntimeRequestMap.size > 0))
) {
throw new Error("codex_run_attach_busy");
}
if (!input.runId) throw new Error("codex_run_attach_invalid");
await this.transport.attachRun?.({
runId: input.runId,
turnId: `turn_attachment_${randomUUID().replaceAll("-", "")}`,
itemId: `item_attachment_${randomUUID().replaceAll("-", "")}`,
});
this.assertProtocolIntegrity();
if (transportOwnsQuiescence) {
// Runnerd's attachment contract performs two durable readiness probes,
// drains the settled provider tail, and rotates authority atomically.
// Its proof supersedes host reducer state that can remain stale when a
// semantic-result consumer stops before the interrupt terminal arrives.
// Drop only the prior run's already-proven-settled buffered suffix.
this.activeTurnId = null;
this.pendingRuntimeRequestMap.clear();
this.eventQueue.clear();
}View on GitHub (pinned to 01ad858492)
Solutions
- Wait for the current turn to reach a terminal/settled state before calling attachRun (await turn completion or the run's result event).
- Resolve or drain pending runtime requests (questions/interrupts) before attaching; if using a transport with attachRun support, ensure quiescence is handled there.
- Retry attachRun after the session reports quiescence (activeTurnId === null and pendingRuntimeRequestMap empty); the error is recoverable by design, not fatal.
- If state appears stale (no actual work running), inspect eventQueue/terminal state and restart the session to clear optimistic state.
Example fix
// before
await session.attachRun({ runId });
// after
if (!session.isIdle()) await waitForTurnSettled(session);
await session.attachRun({ runId }); Defensive patterns
Strategy: retry
Validate before calling
function sessionIsQuiescent(state) {
return !state.turnStartPending && state.activeTurnId === null &&
state.pendingRuntimeRequestCount === 0;
}
if (!sessionIsQuiescent(currentState)) {
throw new Error("attach deferred: session not quiescent");
} Try / catch
try {
await session.attachRun({ runId });
} catch (e) {
if (e.message === "codex_run_attach_busy") {
await waitForTurnSettled(session); // poll events or await turn terminal
await session.attachRun({ runId });
} else throw e;
} Prevention
- Always await the previous turn's terminal event before attaching a new run.
- Serialize attachRun calls through a per-session queue/mutex.
- Resolve or time out pending runtime requests (questions) before recovery attach.
When it happens
Trigger: Calling session.attachRun({ runId }) while turnStartPending is true, or (when transport.attachRun is undefined) while activeTurnId is set to an in-flight turn or pendingRuntimeRequestMap still holds unanswered runtime requests.
Common situations: Orchestration recovery racing with a still-running turn; double-invoking run attach after a timeout without waiting for the previous turn to settle; attaching to a session whose interrupt or runtime question has not been resolved yet; a stale host-side reducer state that thinks the session is idle while a turn is active.
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
- codex_run_attach_invalid
- opencode_run_attach_busy
- Failed to start worktree port reservation lock heartbeat at
- Timed out waiting for worktree port reservation lock at ${lo
- sandbox runtime asset key collides with a reserved runtime a
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/4d7fa56444153e11.
Report an issue: GitHub.