paperclipai/paperclip · error

provider turn ended with status ${turn.status}

Error message

provider turn ended with status ${turn.status}

What it means

After sending the eval prompt, the session's turn result must have status "completed". Any other terminal status (timeout, failed, cancelled, etc.) causes the runner to mark the attempt failed with reason 'provider_turn_<status>' and throw this error. The run then falls into the catch path which writes a failure artifact and exits with code 2.

Source

Thrown at packages/paperclip-runner/src/cli/eval-session.ts:201

      provider: requestedProvider,
      requestedModel: request.model,
      ...(requestedProvider === "acpx"
        ? { acpxAgent: request.acpxAgent ?? "codex" }
        : { acpxAgent: undefined }),
      ...(request.managedProfile === undefined
        ? {}
        : { managedProfile: request.managedProfile }),
      ...(request.agentCoreProfile === undefined
        ? {}
        : { agentCoreProfile: request.agentCoreProfile }),
      attemptId: request.attemptId,
      turnTimeoutMs: request.limits.turnTimeoutMs,
    } as unknown as CreateCapabilityLiveSessionInput;
    session = await service.create(createInput);
    turn = await session.sendMessage(request.prompt);
    if (turn.status !== "completed") {
      await session.completeAttempt("failed", `provider_turn_${turn.status}`);
      throw new Error(`provider turn ended with status ${turn.status}`);
    }
    const usage = evalSessionUsage(request.model, turn.snapshot);
    if (usage.agentTurns > request.limits.maxAgentTurns) {
      throw new Error("agent turn limit exceeded");
    }
    if (
      usage.estimatedCostNanodollars >
      request.limits.maxEstimatedCostNanodollars
    ) {
      throw new Error("estimated cost limit exceeded");
    }
    if (
      usage.providerReportedCostNanodollars >
      request.limits.maxEstimatedCostNanodollars
    ) {
      throw new Error("provider-reported cost limit exceeded");
    }
    await session.completeAttempt("succeeded");

View on GitHub (pinned to 5716fe907e)

Solutions

  1. Inspect the written output artifact (cli.outputPath) — infrastructureFailure classifies retryability and the snapshot/turn fields show what the provider actually did
  2. If failureClass marked it retryable (e.g. 'provider_turn_timeout'), rerun the eval session — timeouts are transient
  3. Increase request.limits.turnTimeoutMs if the provider legitimately needs longer per turn
  4. Verify the provider binary/version is correctly installed and authenticated (e.g. opencode pinned to 1.18.17 per providerVersion)
  5. Check turn/diagnostics in the artifact for provider stderr to fix the underlying provider failure

Example fix

// before (request JSON, tight timeout)
"limits": { "turnTimeoutMs": 30000, ... }
// after
"limits": { "turnTimeoutMs": 300000, ... }
// ...then rerun; for transient provider failures simply retry the session.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight provider sanity check before running the eval session
async function assertProviderReady(provider: string, version: string | null): Promise<void> {
  // e.g. verify binary on PATH and correct pinned version
  const { execFile } = await import("node:child_process");
  const { promisify } = await import("node:util");
  const run = promisify(execFile);
  await run(provider, ["--version"]); // throws early if provider missing/broken
}

Type guard

interface TurnLike { status: string }
function isCompletedTurn(turn: TurnLike): turn is TurnLike & { status: "completed" } {
  return turn.status === "completed";
}

Try / catch

try {
  const code = await runEvalSessionCli(args);
  process.exitCode = code;
} catch (error) {
  if (error instanceof Error && error.message.startsWith("provider turn ended with status")) {
    // Failure artifact was already written to --output; read infrastructureFailure.retryable
    const artifact = JSON.parse(await readFile(outputPath, "utf8"));
    if (artifact.infrastructureFailure?.retryable) {
      // transient (e.g. timeout): retry once with backoff
      process.exitCode = await runEvalSessionCli(args);
    } else {
      console.error(`Non-retryable provider failure: ${artifact.infrastructureError}`);
      process.exitCode = 2;
    }
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: session.sendMessage resolves with turn.status !== "completed" — provider process timed out (turnTimeoutMs exceeded), provider crashed or exited early, or the provider reported a non-success turn outcome.

Common situations: Provider binary hangs or is slow and the turn timeout elapses; provider CLI version/auth broken so the turn errors out; flaky provider infrastructure during eval runs; prompt too large causing provider-side failure.

Related errors


AI-assisted analysis of paperclipai/paperclip@5716fe907e (2026-09-02). Data as JSON: /api/errors/56c299b921e56fb8. Report an issue: GitHub.