{"record":{"id":"bea35009d2f9176e","repo":"paperclipai/paperclip","slug":"capability-live-session-already-has-a-turn-waiter","errorCode":null,"errorMessage":"Capability live session already has a turn waiter","messagePattern":"Capability live session already has a turn waiter","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/live/live-session.ts","lineNumber":1997,"sourceCode":"    }\n    if (persistenceError !== null) {\n      waiter?.reject(\n        persistenceError instanceof Error\n          ? persistenceError\n          : new Error(String(persistenceError)),\n      );\n      throw persistenceError;\n    }\n    this.#emit({\n      turnId: null,\n      kind: \"error\",\n      message: redactCodexDiagnostic(error.message),\n    });\n    waiter?.reject(error);\n  }\n\n  #armTurnWaiter(): Promise<Omit<CapabilityLiveTurnResult, \"snapshot\">> {\n    if (this.#turnWaiter !== null) throw new Error(\"Capability live session already has a turn waiter\");\n    const terminal = new Promise<Omit<CapabilityLiveTurnResult, \"snapshot\">>((resolve, reject) => {\n      const timer = setTimeout(() => {\n        const waiter = this.#turnWaiter;\n        this.#turnWaiter = null;\n        if (waiter !== null) {\n          this.#emit({\n            turnId: this.#activeTurnId,\n            kind: \"error\",\n            message: `Capability Codex turn timed out after ${this.#config.turnTimeoutMs}ms`,\n          });\n        }\n        reject(new Error(`Capability Codex turn timed out after ${this.#config.turnTimeoutMs}ms`));\n      }, this.#config.turnTimeoutMs);\n      this.#turnWaiter = { resolve, reject, timer, assistantText: \"\", draftId: null };\n    });\n    // A caller may await this promise only after another `await` of its own\n    // (see `reconcileActiveTurn`). The timer above can reject before that\n    // point, so attach a no-op handler here, at creation, on every call","sourceCodeStart":1979,"sourceCodeEnd":2015,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/paperclip-runner/src/live/live-session.ts#L1979-L2015","documentation":"Concurrency guard in CapabilityLiveSession.#armTurnWaiter: the session allows only one outstanding turn waiter at a time; arming a second waiter while this.#turnWaiter is still set throws this sentinel. It fires when a new turn is started before the previous turn's waiter was consumed/rejected — i.e. overlapping turn requests on one live session, which would silently lose one turn's terminal result.","triggerScenarios":"Thrown at packages/paperclip-runner/src/live/live-session.ts:1997 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Await the previous turn's completion (or its rejection) before starting another turn on the same live session.","Serialize turn starts behind a queue/lock so #armTurnWaiter cannot run while a waiter is armed.","Check for a path that starts a turn without clearing the waiter on the terminal event."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}