paperclipai/paperclip · error

Codex turn identity changed during start

Error message

Codex turn identity changed during start

What it means

startTurn validates that the turn id returned by the turn/start RPC matches the turn id announced by the optimistic turn/started notification. When the Codex app-server already reports a different active turn than the one the start response returned, failProtocol('turn_start_mismatch') is raised and this Error is thrown, marking the session protocol as broken. It protects against driver/provider disagreement about turn identity, which would corrupt terminal-event routing.

Source

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

      // never observes the terminal turn ahead of turn.accepted.
      releaseTurnStartSettled();
    }
    this.assertProtocolIntegrity();
    const turn = record(response.turn);
    const turnId = text(turn.id);
    if (turnId.length === 0) {
      // A start notification is only optimistic until the response validates.
      // Clear it before released semantic/terminal waiters can observe an
      // active turn for a request that was never accepted.
      this.activeTurnId = null;
      this.turnStarted = false;
      throw new Error("Codex turn response omitted turn.id");
    }
    if (this.activeTurnId !== null && this.activeTurnId !== turnId) {
      this.failProtocol(
        "turn_start_mismatch",
        "turn/start response disagreed with turn/started",
      );
      throw new Error("Codex turn identity changed during start");
    }
    this.activeTurnId ??= turnId;
    if (dispositionOnlyRecovery) {
      this.dispositionOnlyRecoveryTurnId = turnId;
    }
    this.emit("turn.accepted", { turnId }, { turnId });
    if (this.interruptQueued) {
      this.interruptQueued = false;
      await this.#sendInterrupt(turnId, "queued_before_start");
    }
    return {
      turnId,
      effectiveCollaborationMode,
    };
  }

  async steer(input: {

View on GitHub (pinned to 01ad858492)

Solutions

  1. Ensure only one startTurn call is in flight per harness session; await the previous turn's completion or interrupt it before starting a new turn.
  2. Create a fresh harness session for the new turn instead of reusing one that may still have a pending active turn.
  3. Check the Codex app-server version for notification/response ordering bugs and upgrade the provider binary.
  4. Catch the error and restart the session; failProtocol has marked this session terminal so it cannot accept further turns.

Example fix

// before
await Promise.all([session.startTurn(a), session.startTurn(b)]);
// after
await session.startTurn(a);
await session.startTurn(b);
Defensive patterns

Strategy: try-catch

Validate before calling

if (session.hasActiveTurn?.()) throw new Error('session busy: cannot start a new turn');

Type guard

function isTurnIdentityMismatch(e: unknown): boolean { return e instanceof Error && e.message === 'Codex turn identity changed during start'; }

Try / catch

try { await session.startTurn(input); } catch (e) { if (isTurnIdentityMismatch(e)) { await recreateSession(); } else throw e; }

Prevention

When it happens

Trigger: A turn/started notification arrives for a previous or concurrent turn while turn/start is pending, and the turn/start response then returns a different turn id than the one already stored in this.activeTurnId.

Common situations: Calling startTurn concurrently on the same harness session; a stale or buggy Codex app-server version emitting mismatched notifications; a session reused after a prior turn's terminal event was dropped.

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/b3b29bdba42e1183. Report an issue: GitHub.