{"record":{"id":"872890c02c923868","repo":"paperclipai/paperclip","slug":"capability-live-session-already-has-an-active-turn","errorCode":null,"errorMessage":"Capability live session already has an active turn","messagePattern":"Capability live session already has an active turn","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/src/live/live-session.ts","lineNumber":1473,"sourceCode":"  async sendMessage(\n    message: string,\n    /** Launch-only diagnostics may opt out; qualification campaigns must not. */\n    options: { allowMissingUsage?: boolean } = {},\n  ): Promise<CapabilityLiveTurnResult> {\n    const value = message.trim();\n    if (value.length === 0) throw new Error(\"Capability live messages cannot be empty\");\n    if (this.#status === \"suspended\" || this.#transport === null) {\n      await this.#connect(true);\n    }\n    if (this.#transport === null || this.#status === \"closed\" || this.#status === \"failed\") {\n      throw new Error(\"Capability live session is not connected\");\n    }\n    if (\n      this.#activeTurnId !== null ||\n      this.#turnWaiter !== null ||\n      this.#pendingTurnAdmission !== null\n    ) {\n      throw new Error(\"Capability live session already has an active turn\");\n    }\n    let settleAdmission!: () => void;\n    const admission: PendingTurnAdmission = {\n      transport: this.#transport,\n      cancellation: null,\n      settled: new Promise<void>((resolveSettled) => {\n        settleAdmission = resolveSettled;\n      }),\n      settle: () => settleAdmission(),\n    };\n    this.#pendingTurnAdmission = admission;\n    this.#status = \"running\";\n    this.#clearIdleTimer();\n    this.#turnEventCount = 0;\n    this.#turnUsageEventCount = 0;\n    // Publish the user entry before the first admission await. The UI must not\n    // lose the submitted message merely because bounded preflight is slow.\n    const userEntryId = this.#appendTranscript(\"user\", value, null);","sourceCodeStart":1455,"sourceCodeEnd":1491,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/src/live/live-session.ts#L1455-L1491","documentation":"CapabilityLiveSession enforces a single-turn model: only one of #activeTurnId, #turnWaiter, or #pendingTurnAdmission may be outstanding. sendMessage throws when a turn is already active, waiting, or mid-admission, because concurrent user turns on one live session are unsupported.","triggerScenarios":"Calling sendMessage while a previous sendMessage turn has not completed; calling sendMessage while invokeTool's devtools turn is running; racing two sendMessage calls from different code paths without awaiting the first.","commonSituations":"UI double-submitting a chat message; a queue/dispatcher firing a new prompt before the prior turn resolved; automated scripts issuing overlapping prompts to the same session.","solutions":["Serialize sends: await the previous sendMessage promise before issuing the next","Maintain a send mutex/queue around the session so only one turn is ever in flight","Interrupt the current turn first if the new message must preempt it, then send","Use separate sessions if genuinely concurrent turns are needed"],"exampleFix":"// before\nsession.sendMessage('a');\nsession.sendMessage('b'); // throws: turn 'a' still active\n// after\nawait session.sendMessage('a');\nawait session.sendMessage('b');","handlingStrategy":"try-catch","validationCode":"if (session.activeTurnId !== null || session.turnWaiter !== null || session.pendingTurnAdmission !== null) {\n  throw new Error('turn already in flight');\n}","typeGuard":"null","tryCatchPattern":"try {\n  await session.sendMessage(msg);\n} catch (e) {\n  if ((e as Error).message === 'Capability live session already has an active turn') {\n    await enqueueSend(msg); // retry after the current turn drains\n  } else throw e;\n}","preventionTips":["Serialize all turn-initiating calls through one queue per session","Await each sendMessage/invokeTool before issuing the next","Debounce UI submit actions to prevent double sends"],"tags":["concurrency","single-turn","state-machine"],"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"}