paperclipai/paperclip · error

codex_run_attach_invalid

codex_run_attach_invalid

Error message

codex_run_attach_invalid

What it means

attachRun validates its input after the busy check and throws "codex_run_attach_invalid" when input.runId is falsy (empty string, undefined, or null). A run attachment without a run identifier cannot be recorded or matched against usage baselines, so the session rejects it immediately.

Source

Thrown at packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts:93

  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();
    }
    if (this.codexUsageBaseline && input.runId !== this.runId) {
      this.codexUsageBaseline = { baseline: { ...this.codexUsageBaseline.latest }, latest: { ...this.codexUsageBaseline.latest } };

View on GitHub (pinned to 01ad858492)

Solutions

  1. Ensure the run was successfully created and capture its non-empty id before calling attachRun; log the run-creation response to find where the id was lost.
  2. Guard the call site: throw/return early when runId is missing instead of passing it to attachRun.
  3. If runId comes from persistence, verify the stored run row exists and its id column is populated.

Example fix

// before
await session.attachRun({ runId: run.id });
// after
if (!run.id) throw new Error("run id missing before attach");
await session.attachRun({ runId: run.id });
Defensive patterns

Strategy: validation

Validate before calling

function canAttach(run) {
  return typeof run?.id === "string" && run.id.length > 0;
}
if (!canAttach(run)) throw new Error("run must have a non-empty id before attach");

Type guard

function hasRunId(run: unknown): run is { runId: string } {
  return typeof (run as { runId?: unknown })?.runId === "string" &&
    (run as { runId: string }).runId.length > 0;
}

Try / catch

try {
  await session.attachRun({ runId });
} catch (e) {
  if (e.message === "codex_run_attach_invalid") {
    // runId was lost; re-create the run and capture a real id, then retry
    const run = await createRunRecord();
    await session.attachRun({ runId: run.id });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling session.attachRun({ runId: "" }), attachRun({}), or attachRun(undefined as any) — typically when the caller's run record failed to persist and its id came back empty/undefined, or when a variable holding the run id was never initialized.

Common situations: A run-creation API call that returned an error but whose result was destructured anyway; passing a run object's optional id field directly; a regression in orchestration code that lost the runId between run creation and session attach.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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