> ## Documentation Index
> Fetch the complete documentation index at: https://claritylabs.inc/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Checkpoints: When State Is Written, Cleared, and Resumed

> Learn exactly when cl-pipelines writes, updates, and clears checkpoints, and how retryMode controls whether a resumed job loads or discards saved state.

Checkpoints are the mechanism that makes `cl-pipelines` crash-safe. Every checkpoint is a small serialized record stored by your `StorageAdapter`. When a phase completes or explicitly saves progress, the library writes a new checkpoint. When the pipeline finishes, it clears it. Saved checkpoints survive process restarts when your storage adapter persists them. Work performed after the last checkpoint may run again on retry.

## Checkpoint shape

```typescript theme={"system"}
type Checkpoint<TState = unknown> = {
  nextPhase: string;   // name of the next phase to execute
  state: TState;       // arbitrary state between phases
  createdAt: number;   // Unix timestamp (ms)
};
```

The `nextPhase` field is the lookup key `advancePhase` uses to find the right `Phase` object. If you rename a phase in your code after a checkpoint has been written, the resume will fail with a "phase not found" error — handle renames carefully in production.

## When checkpoints are written

| Event | What is stored |
| - | - |
| `runPipeline` called | `{ nextPhase: startPhase, state: initialState, createdAt: now }` |
| Phase returns `{ kind: "next", nextPhase, state }` | `{ nextPhase, state, createdAt: now }` |
| `ctx.saveState(state)` called mid-phase | `{ nextPhase: currentPhase, state, createdAt: now }` |
| Phase throws or returns `{ kind: "error" }` | Checkpoint is **not modified** |
| Phase returns `{ kind: "done" }` | Checkpoint is **cleared** (set to `null`) |

> [!NOTE]
>
> When `ctx.saveState(state)` is called, `nextPhase` is set to the **current** phase name — not the next one. This ensures that a crash re-enters the same phase, not skips ahead to the next one.

## When checkpoints are cleared

The checkpoint is set to `null` and `status` is set to `"complete"` only when a phase returns `{ kind: "done" }`. In all error cases — thrown exceptions or `{ kind: "error" }` returns — the checkpoint is preserved so you can resume later.

## Resume semantics

When you call `runPipeline` on a job that already has a checkpoint, `retryMode` determines what happens:

**resume (default)**

`resolveStartPhase` returns `checkpoint.nextPhase`. The existing checkpoint state is loaded and passed to the phase as `ctx.checkpoint.state`. Only the phase that was running (or waiting to run) at the time of the crash re-executes.

```typescript theme={"system"}
await runPipeline({
  jobId,
  phases,
  storage,
  scheduler,
  initialState,
  retryMode: "resume",
});
```

This is the right default. Use it for your "Retry" button.

**full**

The existing checkpoint is discarded entirely. The pipeline restarts from `initialPhase` (or `phases[0]`) with `initialState`. All previous progress is lost.

```typescript theme={"system"}
await runPipeline({
  jobId,
  phases,
  storage,
  scheduler,
  initialState,
  retryMode: "full",
});
```

Use this when state has become corrupt, when `initialState` has materially changed, or when the user explicitly wants to start over. Label this button "Start over" or "Reset and retry" — not just "Retry".

## Resume example: wiring a retry button

The following shows how you might hook `runPipeline` to a "Retry" button in your application. Because `runPipeline` only writes to storage and enqueues a scheduler event, it's safe to call directly from a UI action or mutation handler:

```typescript theme={"system"}
import { runPipeline } from "@claritylabs/cl-pipelines";

// Wired to a "Retry" button in your UI
async function handleRetry(jobId: string) {
  await runPipeline({
    jobId,
    phases,
    storage,
    scheduler,
    initialState,
    retryMode: "resume",
  });
}
```

> [!TIP]
>
> You don't need to check whether a checkpoint exists before calling `runPipeline` with `retryMode: "resume"`. If there's no checkpoint, the library falls back to starting fresh — the option is safe to use unconditionally as your retry default.

## Inspecting checkpoints in tests

The in-memory storage adapter's `_inspect()` method lets you examine checkpoint state directly in tests:

```typescript theme={"system"}
const jobs = storage._inspect();
const job = jobs.get("my-job")!;

console.log(job.checkpoint?.nextPhase);  // e.g. "enrich"
console.log(job.checkpoint?.state);      // TState at time of last write
console.log(job.checkpoint?.createdAt);  // Unix ms timestamp
```

> [!WARNING]
>
> `storage._inspect()` is a test-only helper. Never read checkpoint state directly in production code — use `StorageAdapter.getJob()` instead.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.