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
- 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.
- Guard the call site: throw/return early when runId is missing instead of passing it to attachRun.
- 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
- Check the run-creation API result for errors before destructuring the id.
- Make runId a required, non-optional type at the call site so TypeScript catches missing ids.
- Assert run creation succeeded (non-empty id) in orchestration tests.
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
- device-login promotion: the account identifier cannot form a
- codex auth cache: account_id is not a valid account handle
- Paperclip Runner currently supports Codex only with codexPer
- Durable PRP bootstrap TTL is invalid.
- persisted Codex ACPX session identity is inconsistent
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/2d61631573997a0d.
Report an issue: GitHub.