{"record":{"id":"b3b29bdba42e1183","repo":"paperclipai/paperclip","slug":"codex-turn-identity-changed-during-start","errorCode":null,"errorMessage":"Codex turn identity changed during start","messagePattern":"Codex turn identity changed during start","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts","lineNumber":268,"sourceCode":"      // never observes the terminal turn ahead of turn.accepted.\n      releaseTurnStartSettled();\n    }\n    this.assertProtocolIntegrity();\n    const turn = record(response.turn);\n    const turnId = text(turn.id);\n    if (turnId.length === 0) {\n      // A start notification is only optimistic until the response validates.\n      // Clear it before released semantic/terminal waiters can observe an\n      // active turn for a request that was never accepted.\n      this.activeTurnId = null;\n      this.turnStarted = false;\n      throw new Error(\"Codex turn response omitted turn.id\");\n    }\n    if (this.activeTurnId !== null && this.activeTurnId !== turnId) {\n      this.failProtocol(\n        \"turn_start_mismatch\",\n        \"turn/start response disagreed with turn/started\",\n      );\n      throw new Error(\"Codex turn identity changed during start\");\n    }\n    this.activeTurnId ??= turnId;\n    if (dispositionOnlyRecovery) {\n      this.dispositionOnlyRecoveryTurnId = turnId;\n    }\n    this.emit(\"turn.accepted\", { turnId }, { turnId });\n    if (this.interruptQueued) {\n      this.interruptQueued = false;\n      await this.#sendInterrupt(turnId, \"queued_before_start\");\n    }\n    return {\n      turnId,\n      effectiveCollaborationMode,\n    };\n  }\n\n  async steer(input: {","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts#L250-L286","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Create a fresh harness session for the new turn instead of reusing one that may still have a pending active turn.","Check the Codex app-server version for notification/response ordering bugs and upgrade the provider binary.","Catch the error and restart the session; failProtocol has marked this session terminal so it cannot accept further turns."],"exampleFix":"// before\nawait Promise.all([session.startTurn(a), session.startTurn(b)]);\n// after\nawait session.startTurn(a);\nawait session.startTurn(b);","handlingStrategy":"try-catch","validationCode":"if (session.hasActiveTurn?.()) throw new Error('session busy: cannot start a new turn');","typeGuard":"function isTurnIdentityMismatch(e: unknown): boolean { return e instanceof Error && e.message === 'Codex turn identity changed during start'; }","tryCatchPattern":"try { await session.startTurn(input); } catch (e) { if (isTurnIdentityMismatch(e)) { await recreateSession(); } else throw e; }","preventionTips":["Never call startTurn concurrently on one harness session","Await terminal events before starting the next turn","Keep the Codex app-server binary up to date","Recreate sessions after any protocol error — failProtocol marks them terminal"],"tags":["protocol","race-condition","codex"],"backgroundTag":"invalid-state-transition","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}