paperclipai/paperclip · error

Codex turn response omitted turn.id

Error message

Codex turn response omitted turn.id

What it means

When the Codex turn.start RPC responds, the session reads response.turn.id and requires a non-empty id. An empty or missing id means the response shape is not what the Codex live transport contract promises, so the session rejects the pending turn, releases the admission, and throws.

Source

Thrown at packages/paperclip-runner/src/live/live-session.ts:1583

        throw error;
      }
      if (admission.cancellation !== null) {
        return this.#completeInterruptedAdmission(admission, userEntryId);
      }
      this.#rejectTurn(error);
      this.#releaseTurnAdmission(admission);
      await terminal.catch(() => undefined);
      throw error;
    }
    if (this.#pendingTurnAdmission !== admission) {
      await terminal.catch(() => undefined);
      throw new Error("Capability live turn start completed after admission teardown");
    }
    const turnId = text(record(response.turn).id);
    if (turnId.length === 0) {
      this.#rejectTurn(new Error("Codex turn response omitted turn.id"));
      this.#releaseTurnAdmission(admission);
      throw new Error("Codex turn response omitted turn.id");
    }
    if (this.#activeTurnId !== null && this.#activeTurnId !== turnId) {
      this.#rejectTurn(new Error("Codex turn identity changed during start"));
      this.#releaseTurnAdmission(admission);
      throw new Error("Codex turn identity changed during start");
    }
    if (this.#turnWaiter !== null) this.#activeTurnId = turnId;
    this.#bindAdmissionUser(userEntryId, turnId);
    const draftId = (this.#turnWaiter as TurnWaiter | null)?.draftId ?? null;
    const draft =
      draftId === null ? undefined : this.#transcript.find((entry) => entry.id === draftId);
    if (draft !== undefined && draft.turnId === null) draft.turnId = turnId;
    try {
      await this.#persist();
    } catch (error) {
      this.#rejectTurn(error);
      this.#releaseTurnAdmission(admission);
      await terminal.catch(() => undefined);

View on GitHub (pinned to 01ad858492)

Solutions

  1. Check the Codex adapter/agent version matches what CapabilityLiveSession expects and upgrade/downgrade accordingly
  2. Log and inspect the raw turn.start response to confirm the turn.id field name and shape
  3. Fix test stubs/mocks to return a turn object with a non-empty id
  4. Add schema validation on the transport layer to fail fast with a clearer payload error

Example fix

// before (test stub)
{ turn: {} } // omitted turn.id -> throws
// after
{ turn: { id: 'turn_123', status: 'running' } }
Defensive patterns

Strategy: validation

Validate before calling

const turn = (response as { turn?: { id?: unknown } }).turn;
if (typeof turn?.id !== 'string' || turn.id.length === 0) throw new Error('turn.start response missing turn.id');

Type guard

function hasTurnId(r: unknown): r is { turn: { id: string } } {
  const t = (r as any)?.turn;
  return !!t && typeof t.id === 'string' && t.id.length > 0;
}

Try / catch

try {
  await session.sendMessage(msg);
} catch (e) {
  if ((e as Error).message === 'Codex turn response omitted turn.id') {
    // inspect raw response; verify adapter/Codex versions before retry
  } else throw e;
}

Prevention

When it happens

Trigger: Codex returns a turn object without an id field, or id is empty string; response deserialized into { turn: {} } due to a protocol/adapter version mismatch.

Common situations: Codex agent/adapter version drift where the turn envelope changed; mocked or stubbed Codex transport in tests returning incomplete turn payloads; corrupted/truncated RPC response.

Related errors


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