paperclipai/paperclip · error

capability_live_attempt_active_turn

capability_live_attempt_active_turn

Error message

capability_live_attempt_active_turn

What it means

CapabilityLiveSession.finishAttempt (or equivalent attempt-terminating path) enforces that a live session attempt cannot be marked 'succeeded' while a turn is still in flight. The guard checks #activeTurnId, #turnWaiter, and #pendingTurnAdmission; if any is set, the success transition is illegal because the session still has work that has not settled.

Source

Thrown at packages/paperclip-runner/src/live/live-session.ts:1444

    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();
  }

  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) {

View on GitHub (pinned to 01ad858492)

Solutions

  1. Wait for the active turn (or turn waiter / pending admission) to settle before marking the attempt succeeded
  2. Interrupt or cancel the active turn first (session interrupt path), then finish the attempt
  3. If the attempt actually failed, finish with status='failed' which is allowed while a turn is active
  4. Audit the ordering in supervisor/teardown code so attempt completion always happens after turn drain

Example fix

// before
session.finishAttempt('succeeded'); // may throw while a turn is running
// after
await session.waitForIdleTurn(); // resolves when #activeTurnId/#turnWaiter/#pendingTurnAdmission are null
session.finishAttempt('succeeded');
Defensive patterns

Strategy: try-catch

Validate before calling

const idle = session.activeTurnId === null && session.turnWaiter === null && session.pendingTurnAdmission === null;
if (!idle) throw new Error('attempt still has an active turn');

Type guard

function isIdle(session: { status: string; hasActiveTurn?: boolean }): boolean {
  return !session.hasActiveTurn && session.status !== 'closed' && session.status !== 'failed';
}

Try / catch

try {
  session.finishAttempt('succeeded');
} catch (e) {
  if ((e as Error).message === 'capability_live_attempt_active_turn') {
    await session.waitForIdleTurn();
    session.finishAttempt('succeeded');
  } else throw e;
}

Prevention

When it happens

Trigger: Calling the attempt-finish API with status='succeeded' while a sendMessage/invokeTool turn is running, a turn waiter is awaiting completion, or a turn admission is still pending. Marking success from a concurrent teardown path without first interrupting/draining the active turn.

Common situations: Race between a session supervisor deciding the attempt succeeded and a still-open Codex turn; cleanup code that closes the attempt before resolving the active turn; tests that forget to await sendMessage before finishing the attempt.

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


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/dd6f53826ad2889c. Report an issue: GitHub.