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

  1. Wait for the current turn to reach a terminal/settled state before calling attachRun (await turn completion or the run's result event).
  2. Resolve or drain pending runtime requests (questions/interrupts) before attaching; if using a transport with attachRun support, ensure quiescence is handled there.
  3. Retry attachRun after the session reports quiescence (activeTurnId === null and pendingRuntimeRequestMap empty); the error is recoverable by design, not fatal.
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/4d7fa56444153e11. Report an issue: GitHub.