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
- Configure the provider recovery policy to 'allow_replacement_after_resume_failure' if starting a replacement provider session is acceptable for this workload
- Investigate why the provider session failed (check provider logs/exit state in the checkpoint's terminal.runTerminalState) before resuming
- Rerun the task from scratch rather than resuming, since recovery is blocked for failed provider sessions
- If failures cluster after a provider upgrade, pin or roll back the provider version and re-checkpoint sessions
- 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
- Set providerRecoveryPolicy to 'allow_recovery_after_resume_failure' when replacement sessions are acceptable
- Capture provider logs and terminal state to diagnose why the session failed before resuming
- Pin provider versions so upgrades do not strand in-flight sessions in failed terminals
- Treat this error as non-retryable — do not loop on resume attempts
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
- native_runner_warm_transition_completion_pending
- [paperclip] sandbox callback bridge kept queued request ${re
- [paperclip] sandbox callback bridge failed to abort queued r
- Refusing embedded PostgreSQL recovery because the data direc
- failed company transfer runs stranded in applying by a resta
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/027d32d883c18df2.
Report an issue: GitHub.