paperclipai/paperclip · error
capability_live_attempt_not_running
capability_live_attempt_not_running
Error message
capability_live_attempt_not_running
What it means
completeAttempt on the capability live session finalizes the current attempt with a terminal status (succeeded/failed etc.). It first finds the attempt matching #currentAttemptId and requires it to still be in 'running' state; if there is no current attempt or it has already been terminated/completed, it throws the machine-readable error 'capability_live_attempt_not_running'. This enforces that an attempt can only be completed exactly once from the running state.
Source
Thrown at packages/paperclip-runner/src/live/live-session.ts:1436
observedAt: undefined,
});
if (comparable(existing) !== comparable(receipt)) {
throw new Error("capability_live_usage_receipt_conflict");
}
return "duplicate";
}
this.#usageLedger.push(receipt);
await this.#persist();
return "committed";
}
async completeAttempt(
status: Exclude<CapabilityLiveAttemptStatus, "running" | "terminated">,
failureCode: string | null = null,
): Promise<CapabilityLiveSessionSnapshot> {
const attempt = this.#attempts.find((candidate) => candidate.attemptId === this.#currentAttemptId);
if (attempt === undefined || attempt.status !== "running") {
throw new Error("capability_live_attempt_not_running");
}
if (
status === "succeeded" &&
(this.#activeTurnId !== null ||
this.#turnWaiter !== null ||
this.#pendingTurnAdmission !== null)
) {
throw new Error("capability_live_attempt_active_turn");
}
attempt.status = status;
attempt.finishedAt = this.#now().toISOString();
attempt.failureCode = status === "failed"
? requireNonEmpty(failureCode ?? "attempt_failed", "attempt_failure_code")
: null;
await this.#persist();
return this.snapshot();
}
View on GitHub (pinned to 01ad858492)
Solutions
- Check attempt status before calling: only invoke completeAttempt when the snapshot shows the current attempt as 'running'.
- Make completion idempotent in the caller: track whether completeAttempt already succeeded and swallow the second call.
- Ensure timeout/termination paths and normal completion paths are mutually exclusive (single completion owner per attempt).
- Wrap in try-catch for this code and treat it as a benign race during shutdown.
Example fix
// before
await session.completeAttempt('succeeded');
...
await session.completeAttempt('failed', 'timeout'); // throws
// after
const snap = session.snapshot();
const current = snap.attempts.find((a) => a.attemptId === snap.currentAttemptId);
if (current?.status === 'running') await session.completeAttempt('failed', 'timeout'); Defensive patterns
Strategy: try-catch
Validate before calling
const snap = session.snapshot(); const attempt = snap.attempts.find((a) => a.attemptId === snap.currentAttemptId); const canComplete = attempt !== undefined && attempt.status === 'running';
Type guard
function attemptIsRunning(snap: CapabilityLiveSessionSnapshot): boolean {
const a = snap.attempts.find((x) => x.attemptId === snap.currentAttemptId);
return a !== undefined && a.status === 'running';
} Try / catch
try {
await session.completeAttempt(status, failureCode);
} catch (err) {
if ((err as Error).message === 'capability_live_attempt_not_running') {
logger.info('attempt already terminal; completion race ignored');
return session.snapshot();
}
throw err;
} Prevention
- Designate a single owner (timeout handler OR completion handler, not both) per attempt.
- Check the attempt status in the session snapshot before completing.
- Make completion idempotent: remember whether completeAttempt already ran.
- Serialize completion calls through a promise/lock to avoid double delivery races.
When it happens
Trigger: Calling completeAttempt twice for the same attempt; calling it after terminateAttempt/timeout already moved the attempt out of 'running'; calling it when no attempt was ever started (#currentAttemptId is null); racing completion against a session restart that reset current-attempt tracking.
Common situations: Double callback delivery from a provider client (error + success both triggering completion); watchdog timeout firing while the model also returns; retry logic that re-completes a stale attempt handle; resuming a session from a snapshot where the attempt was already terminal.
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
- Warm transition result is not yet authenticated.
- ACPX provider ownership admission is already active
- capability_live_attempt_active_turn
- Capability live turn admission was abandoned during teardown
- Chat SDK endpoint runtime was retired
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/c7633a000a0f1890.
Report an issue: GitHub.