paperclipai/paperclip · error · NativeProviderTerminalFailure

provider_checkpoint_failed_terminal

provider_checkpoint_failed_terminal

Error message

provider_checkpoint_failed_terminal

What it means

During provider recovery checkpoint evaluation in the native session runtime, a checkpoint whose terminal.runTerminalState is 'failed' with a null semanticResult marks the provider session as terminally failed. If the checkpoint's providerRecoveryPolicy is not 'allow_replacement_after_resume_failure', the runtime throws NativeProviderTerminalFailure with code provider_checkpoint_failed_terminal (retryable=false), refusing to resume a dead provider session. When replacement IS allowed, it instead returns recovered:false with a reason instead of throwing.

Source

Thrown at packages/paperclip-runner/src/native-session-runtime.ts:1852

        reconcileRecoveryCursor({
          controlPlane: options.controlPlane,
          checkpoint: recoveryCheckpoint,
          runId: input.binding.runId,
          sourceInstanceId: options.runnerInstanceId,
          signal,
        }),
    });
    reconciledRecoveryCheckpoint = persistedSession;
    const providerRecoveryCheckpoint = persistedSession;

    const replacementAllowed =
      providerRecoveryCheckpoint.providerRecoveryPolicy ===
      "allow_replacement_after_resume_failure";
    const failedProviderSession =
      providerRecoveryCheckpoint.terminal?.runTerminalState === "failed" &&
      providerRecoveryCheckpoint.semanticResult === null;
    if (failedProviderSession && !replacementAllowed) {
      throw new NativeProviderTerminalFailure("provider_checkpoint_failed_terminal", false);
    }
    const recovery = failedProviderSession
      ? {
          recovered: false as const,
          reason: "provider session ended with a failed terminal",
        }
      : options.backend.recoverSession
        ? await runAbortableOperationWithin({
            timeoutMs: recoveryTimeoutMs,
            timeoutMessage: `native session provider recovery timed out after ${recoveryTimeoutMs}ms`,
            operation: (signal) =>
              options.backend.recoverSession!(providerRecoveryCheckpoint, {
                signal,
              }),
            onLateResolution: async (lateRecovery) => {
              if (lateRecovery.session) {
                await disposeUnadmittedSession(
                  lateRecovery.session,

View on GitHub (pinned to 01ad858492)

Solutions

  1. Configure the provider recovery policy to 'allow_replacement_after_resume_failure' if starting a replacement provider session is acceptable for this workload
  2. Investigate why the provider session failed (check provider logs/exit state in the checkpoint's terminal.runTerminalState) before resuming
  3. Rerun the task from scratch rather than resuming, since recovery is blocked for failed provider sessions
  4. If failures cluster after a provider upgrade, pin or roll back the provider version and re-checkpoint sessions
  5. Handle NativeProviderTerminalFailure as non-retryable in the calling loop — retrying resume will throw again

Example fix

// before (policy blocks replacement)
providerRecoveryPolicy: 'never_replace';
// after
providerRecoveryPolicy: 'allow_replacement_after_resume_failure';
// resume then returns { recovered: false, reason: 'provider session ended with a failed terminal' } instead of throwing
Defensive patterns

Strategy: try-catch

Validate before calling

const cp = await loadProviderRecoveryCheckpoint(runId); const terminal = cp.terminal?.runTerminalState === 'failed' && cp.semanticResult === null; if (terminal && cp.providerRecoveryPolicy !== 'allow_replacement_after_resume_failure') throw new Error('provider session terminally failed; replacement not allowed');

Type guard

function isProviderTerminalFailure(e: unknown) { return e instanceof NativeProviderTerminalFailure && e.message === 'provider_checkpoint_failed_terminal'; }

Try / catch

try { await resumeRun(runId); } catch (e) { if (isProviderTerminalFailure(e)) { return restartRunFromScratch(runId); } throw e; }

Prevention

When it happens

Trigger: Resuming a run whose provider session (e.g. a CLI agent process) ended in a failed terminal state and produced no semantic result, while the recovery policy for that provider does not permit replacement after resume failure — e.g. resume-after-crash of a session that exited non-zero with no usable output.

Common situations: Provider CLI crashed or was OOM-killed mid-run; provider exited with a fatal error before emitting a result; resuming old sessions after a provider version upgrade that changed exit semantics; policy not configured to allow replacement, so the runtime treats the failure as terminal instead of silently substituting a new 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


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