paperclipai/paperclip · error
Capability live session already has an active turn
Error message
Capability live session already has an active turn
What it means
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.
Source
Thrown at packages/paperclip-runner/src/live/live-session.ts:1473
async sendMessage(
message: string,
/** Launch-only diagnostics may opt out; qualification campaigns must not. */
options: { allowMissingUsage?: boolean } = {},
): Promise<CapabilityLiveTurnResult> {
const value = message.trim();
if (value.length === 0) throw new Error("Capability live messages cannot be empty");
if (this.#status === "suspended" || this.#transport === null) {
await this.#connect(true);
}
if (this.#transport === null || this.#status === "closed" || this.#status === "failed") {
throw new Error("Capability live session is not connected");
}
if (
this.#activeTurnId !== null ||
this.#turnWaiter !== null ||
this.#pendingTurnAdmission !== null
) {
throw new Error("Capability live session already has an active turn");
}
let settleAdmission!: () => void;
const admission: PendingTurnAdmission = {
transport: this.#transport,
cancellation: null,
settled: new Promise<void>((resolveSettled) => {
settleAdmission = resolveSettled;
}),
settle: () => settleAdmission(),
};
this.#pendingTurnAdmission = admission;
this.#status = "running";
this.#clearIdleTimer();
this.#turnEventCount = 0;
this.#turnUsageEventCount = 0;
// Publish the user entry before the first admission await. The UI must not
// lose the submitted message merely because bounded preflight is slow.
const userEntryId = this.#appendTranscript("user", value, null);View on GitHub (pinned to 01ad858492)
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
Example fix
// before
session.sendMessage('a');
session.sendMessage('b'); // throws: turn 'a' still active
// after
await session.sendMessage('a');
await session.sendMessage('b'); Defensive patterns
Strategy: try-catch
Validate before calling
if (session.activeTurnId !== null || session.turnWaiter !== null || session.pendingTurnAdmission !== null) {
throw new Error('turn already in flight');
} Type guard
null
Try / catch
try {
await session.sendMessage(msg);
} catch (e) {
if ((e as Error).message === 'Capability live session already has an active turn') {
await enqueueSend(msg); // retry after the current turn drains
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- capability_live_attempt_active_turn
- Failed to start worktree port reservation lock heartbeat at
- Timed out waiting for worktree port reservation lock at ${lo
- sandbox runtime asset key collides with a reserved runtime a
- DUPLEX_CHANNEL_ROUTE_BUSY
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/872890c02c923868.
Report an issue: GitHub.