paperclipai/paperclip · error
Capability live turn start completed after admission teardow
Error message
Capability live turn start completed after admission teardown
What it means
After the Codex turn.start RPC resolves, sendMessage re-checks that its admission is still the session's current pending admission. If teardown released the admission while the start request was in flight, the turn result arrives too late to be bound, so the method cancels the terminal promise, releases resources, and throws.
Source
Thrown at packages/paperclip-runner/src/live/live-session.ts:1577
runtimeWorkspaceRoots: [this.#config.workingDirectory],
input: [userInput(value)],
});
} catch (error) {
if (this.#pendingTurnAdmission !== admission) {
await terminal.catch(() => undefined);
throw error;
}
if (admission.cancellation !== null) {
return this.#completeInterruptedAdmission(admission, userEntryId);
}
this.#rejectTurn(error);
this.#releaseTurnAdmission(admission);
await terminal.catch(() => undefined);
throw error;
}
if (this.#pendingTurnAdmission !== admission) {
await terminal.catch(() => undefined);
throw new Error("Capability live turn start completed after admission teardown");
}
const turnId = text(record(response.turn).id);
if (turnId.length === 0) {
this.#rejectTurn(new Error("Codex turn response omitted turn.id"));
this.#releaseTurnAdmission(admission);
throw new Error("Codex turn response omitted turn.id");
}
if (this.#activeTurnId !== null && this.#activeTurnId !== turnId) {
this.#rejectTurn(new Error("Codex turn identity changed during start"));
this.#releaseTurnAdmission(admission);
throw new Error("Codex turn identity changed during start");
}
if (this.#turnWaiter !== null) this.#activeTurnId = turnId;
this.#bindAdmissionUser(userEntryId, turnId);
const draftId = (this.#turnWaiter as TurnWaiter | null)?.draftId ?? null;
const draft =
draftId === null ? undefined : this.#transcript.find((entry) => entry.id === draftId);
if (draft !== undefined && draft.turnId === null) draft.turnId = turnId;View on GitHub (pinned to 01ad858492)
Solutions
- Avoid closing/failing the session while a turn.start request is outstanding
- Treat the thrown error as 'turn not started' and abandon the old session; retry on a fresh session
- Await sendMessage before invoking any teardown path; use lifecycle coordination (e.g. a busy flag)
- Investigate why turn.start was slow if this happens without explicit teardown
Example fix
// before
const p = session.sendMessage(msg);
session.fail(new Error('timeout')); // races turn.start
await p; // throws 'completed after admission teardown'
// after
await session.sendMessage(msg); // then decide about teardown
session.fail(new Error('timeout')); Defensive patterns
Strategy: try-catch
Validate before calling
if (session.status === 'closed' || session.status === 'failed') throw new Error('session teardown in progress; abort send'); Type guard
null
Try / catch
try {
await session.sendMessage(msg);
} catch (e) {
if ((e as Error).message === 'Capability live turn start completed after admission teardown') {
session = await createLiveSession(opts); // retry on a fresh session
} else throw e;
} Prevention
- Hold a teardown lock that sendMessage acquires so close/fail cannot interleave with turn.start
- Add timeouts on turn.start so slow starts do not invite concurrent teardowns
- Log admission lifecycle to spot racing teardown paths
When it happens
Trigger: Session close/fail/interrupt completes between admission and the return of the turn.start RPC response; a slow turn.start response overlapping a teardown triggered elsewhere.
Common situations: Slow agent process responding to turn.start while a user or supervisor tears the session down; flaky transport causing long start latency and a concurrent timeout-triggered close.
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 turn admission was abandoned during teardown
- ${name} must be a JSON object
- Invalid ${name} JSON: ${err instanceof Error ? err.message :
- --file is required
- Request failed with status ${response.status}
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/b24f58cd35331809.
Report an issue: GitHub.