Headless Notes

Practical guides to running AI automations that keep working while you sleep.

"Claude Code Checkpoints: How Rollback Works in Automations"

"Claude Code Checkpoints: How Rollback Works in Automations"

2026-10-14 · 15 min read

#claude code #headless #agent sdk #automation

In interactive Claude Code, checkpoints are on by default, and you undo with /rewind or by pressing Esc twice when the prompt is empty. Headless runs (claude -p or the Agent SDK) have nobody to press that key. You have to turn file checkpointing on, save the first user-message UUID and the session ID, and have your script call rewindFiles() / rewind_files() or run claude -p --resume <session-id> --rewind-files <checkpoint-uuid>. Rewind only restores edits made with Write, Edit and NotebookEdit. It doesn't cover Bash, most subagent edits or directories, so git stays your main safety net.

Which option is right for you?

Your setup depends on what the job does: rewind fully covers only jobs that edit files through Claude's own edit tools.

Your unattended job mostly…Main safety netRole of checkpoint rewind
Edits files (refactors, doc updates, lint fixes made with Edit/Write)Rewind, with a git commit underneathPrimary undo. Fast, and it can go back to a middle turn
Runs shell commands that change files (formatters, codemods, npm install, sed -i, mv)git, or a worktree you can throw awayPartial at best. Bash changes are not tracked
Uses subagents or background forked skillsgit, or a worktreeAssume it won't restore their edits
Touches things outside the repo (APIs, databases, deploys)Permission limits and dry runsNone. Rewind only covers local files

If you're not sure, start every run on a clean git commit or in a worktree, and treat rewind as a convenience on top.

A detail people often miss: --permission-mode acceptEdits also auto-approves common filesystem commands such as mkdir, touch, mv and cp (Headless docs). Those run through Bash, so rewind can't undo them even in a job you thought "only edits files."

What a checkpoint is and what it isn't

A checkpoint is a snapshot of files Claude edited with its file tools. It is not a full backup of your working tree. In interactive mode, Claude Code takes one each time you send a prompt that starts a turn. It keeps file snapshots for the 100 most recent checkpoints in a session and saves them with the conversation, so /rewind still works after you resume (Checkpointing docs). The rewind menu can restore code and conversation, only the conversation, or only the code. It can also summarize from or up to a chosen point (same source).

These limits matter more when nobody is watching:

The fileCheckpointingEnabled setting turns the interactive /rewind snapshots on or off. When it is unset, snapshots are on (Settings reference).

How headless checkpointing differs

In the Agent SDK, checkpointing is opt-in, and it takes two options (SDK docs):

PurposePythonTypeScript
Track file changesenable_file_checkpointing=TrueenableFileCheckpointing: true
Get checkpoint UUIDs in the streamextra_args={"replay-user-messages": None}extraArgs: { "replay-user-messages": null }

With both set, each user message in the response stream carries a uuid you can use as a restore point. Rewinding to the first user message's UUID returns tracked files to their original state. Rewinding files doesn't rewind the conversation, which stays as it was (SDK docs).

The official examples also set permission_mode="acceptEdits" so edits go through without a prompt (SDK docs). Always pass a permission mode explicitly. A -p run that sets none starts in a built-in default mode, and that default can be auto (Headless docs).

Pattern 1: Rewind inside the run when validation fails

Use this pattern when your script can tell during the run that something has gone wrong. Examples: a test command fails, a protected file changes, or the diff is much larger than expected.

The docs' "checkpoint before risky operations" pattern keeps a user-message UUID and calls rewind_files() as soon as your own condition is true, while the stream is still open (SDK docs). Here is a shortened Python version. should_revert() is a placeholder for your own check:

options = ClaudeAgentOptions(
    enable_file_checkpointing=True,
    permission_mode="acceptEdits",
    extra_args={"replay-user-messages": None},
)

first_checkpoint = None
async with ClaudeSDKClient(options) as client:
    await client.query("Update dependencies and fix type errors")
    async for message in client.receive_response():
        if isinstance(message, UserMessage) and message.uuid and not first_checkpoint:
            first_checkpoint = message.uuid
        if should_revert(message) and first_checkpoint:   # your own check
            await client.rewind_files(first_checkpoint)
            break

You need to make two decisions:

  1. Pick which checkpoint to rewind to. The docs' version overwrites its variable every turn, so it keeps only the latest checkpoint and undoes only the last step. An unattended job usually wants to throw away the whole attempt, so the example above keeps the first UUID. To keep some turns and undo others, store every UUID. The docs call this "Multiple restore points" (SDK docs).
  2. Call rewind before the loop ends. If you call rewindFiles() after you've finished iterating, you get "ProcessTransport is not ready for writing," because the connection closes when the loop completes (SDK docs).

Pattern 2: Record IDs now, rewind later

Use this pattern when you only find out a run was bad after it ends, for example when CI goes red an hour later or someone reviews the diff the next morning.

  1. Save checkpoint_id and session_id for every run. A small JSON log next to the job output is enough. The docs show how to capture session_id from the ResultMessage (SDK docs).
  2. Rewind later by resuming with an empty prompt. Open a new client with enable_file_checkpointing=True and resume=session_id, send "", and call rewind_files(checkpoint_id) inside the response loop (SDK docs).
  3. Or rewind from the shell, without SDK code:
CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true \
  claude -p --resume <session-id> --rewind-files <checkpoint-uuid>

--rewind-files doesn't appear in claude --help, but the CLI accepts it. When it works, it prints Files rewound to state at message <checkpoint-uuid> and exits without sending a prompt. Without the environment variable, the bare CLI fails with "File rewinding is not enabled" (SDK docs).

Run the rewind on the same machine that ran the job. Checkpoints belong to the session that created them (SDK docs), and the snapshots live in local Claude Code state (Checkpointing docs). --resume <id> can find a session in other projects on the same machine (CLI reference). Running the rewind from the original project directory is still the safest choice.

Let the run finish before you depend on its checkpoints. "No file checkpoint found" can mean the session wasn't properly completed (SDK docs), and a claude -p run stopped with SIGTERM leaves its current turn unfinished (Headless docs). If your supervisor has to stop a job, send SIGINT or call the SDK's interrupt() first, as the headless docs recommend.

Using only the CLI, without the SDK

You can do this from a shell script. --replay-user-messages requires both --input-format stream-json and --output-format stream-json. You can also give the session a known ID up front with --session-id <uuid> so you don't have to parse it from the output (CLI reference).

The SDK sets CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING internally only when checkpointing is enabled, and the bare CLI never sets it (SDK docs). "No file checkpoint found" can mean checkpointing was off on the original session, so the safe reading is to set the variable on the original run as well as on the rewind. The docs don't describe this CLI-only flow end to end, so this is an inference. Test it on a throwaway repo before you rely on it.

Here is an example launch. The headless page doesn't show the stream-json input message format, so check it against the Agent SDK docs before use:

SID=$(uuidgen | tr 'A-Z' 'a-z')
echo '{"type":"user","message":{"role":"user","content":"Fix lint errors in src/"}}' | \
CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true claude -p \
  --session-id "$SID" \
  --input-format stream-json --output-format stream-json --verbose \
  --replay-user-messages --permission-mode acceptEdits \
  > run.jsonl
CHECKPOINT=$(jq -r 'select(.type=="user" and .uuid) | .uuid' run.jsonl | head -1)
echo "$SID $CHECKPOINT" >> agent-runs.log

Don't use --no-session-persistence on runs you might want to rewind. It stops the session from being saved to disk, so the session can't be resumed (CLI reference), and a later rewind depends on resuming.

Rewind vs. git vs. worktrees: which to use when

Use all of them together. Each one covers gaps the others leave.

MechanismCoversMissesBest for
Checkpoint rewindWrite/Edit/NotebookEdit changes to local files (SDK docs)Bash changes, most subagent edits, directories, links, network filesFast, precise undo of the agent's own edits, including going back to a middle turn
Git commit/branch before the runEverything in the repo that git tracksUntracked/ignored files unless you include them; anything outside the repoThe baseline you can always reset to
--worktree / -wRuns in an isolated worktree at <repo>/.claude/worktrees/<name> (CLI reference)Side effects outside the repo (APIs, databases)Recurring jobs that should never touch your main checkout
Permission limitsBlocks risky actions up front, e.g. --disallowedTools "Bash(rm *)", --permission-prompts none (v2.1.259+) (CLI reference; Headless docs)Mistakes made with allowed toolsKeeping the damage within what rewind and git can undo

How to decide:

A practical setup checklist

  1. Start each run on a clean git state, or in a worktree with claude -w <name> (CLI reference).
  2. Turn on checkpointing (enableFileCheckpointing / enable_file_checkpointing, or the env var for the bare CLI) and replay-user-messages (SDK docs).
  3. Set the permission mode explicitly (Headless docs).
  4. Save the first user-message UUID and the session ID for every run.
  5. Limit runaway runs with --max-turns and --max-budget-usd, which work in print mode only. The budget cap uses a client-side cost estimate that can differ from your bill, and spend can go past it, so leave headroom (CLI reference).
  6. Add a validation step (tests, lint, diff-size check) and rewind automatically when it fails.
  7. After any rewind, run git status. Anything still changed came from Bash, a subagent or a skipped path. Reset it with git.
  8. Read skippedLinks in RewindFilesResult and alert when it isn't zero, because those files kept their current contents (SDK docs).

Troubleshooting quick reference

Most rewind failures come from a missing option on the original run or from calling rewind at the wrong time.

SymptomLikely causeFix
message.uuid missingreplay-user-messages not setAdd it to extraArgs / extra_args (SDK docs)
"No file checkpoint found for this message"Checkpointing was off on the original session, or the session didn't finishEnable it on the original run and let the session finish before resuming (same)
"File rewinding is not enabled"Bare CLI without the env var, or a resumed SDK session without the optionSet CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true, or enable the option on the resumed session (same)
"ProcessTransport is not ready for writing"Rewind called after the loop endedResume with an empty prompt, then rewind (same)
"No files were restored"Snapshots removed by retention cleanupRaise cleanupPeriodDays; use git for this run (Checkpointing docs)
Files changed after a "successful" rewindBash edits (including mv/cp allowed by acceptEdits), subagent edits, or skipped pathsgit status, then git checkout/git clean the leftovers
Options not recognizedOld SDKpip install --upgrade claude-agent-sdk or npm install @anthropic-ai/claude-agent-sdk@latest (SDK docs)

FAQ

Are checkpoints on by default in claude -p or the Agent SDK?

No. Interactive sessions take snapshots by default. In the Agent SDK you have to enable file checkpointing and replay-user-messages, and with the bare CLI you set CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true (SDK docs).

Can I skip git if I use checkpoints?

No. Anthropic says checkpoints are for quick, session-level recovery and recommends git for permanent history. Rewind also misses Bash changes, most subagent edits and directory operations (Checkpointing docs).

Does rewinding files also rewind the conversation?

Not in the SDK. rewindFiles() / rewind_files() restores files on disk and leaves the conversation history as it was. The interactive /rewind menu lets you choose code, conversation, or both (SDK docs; Checkpointing docs).

How long can I wait before rewinding a run?

By default, about 30 days after the session last saved a snapshot. After that, cleanup can remove the snapshots. You can change this with cleanupPeriodDays (Checkpointing docs).

Can I rewind on a different machine, such as a fresh CI runner?

Not reliably. Checkpoints are tied to the session that created them, and the snapshots live in local Claude Code state (SDK docs; Checkpointing docs). On short-lived runners, rely on git or worktrees instead.

Next steps

Pick the job type that matches your automation and try only the checklist items for that type today. Each list takes one throwaway repo and one run.

If your job mostly edits files (Edit/Write):

  1. Copy the Pattern 1 code. Replace should_revert() with one check, such as "tests fail."
  2. Run it once on a throwaway repo. Make the check fail on purpose and confirm the files go back.
  3. Run git status afterwards. If something is still changed, find which command caused it.

If your job mostly runs Bash commands:

  1. Wrap the run in claude -w <name>, or commit before each run.
  2. Write a one-line revert script: git reset --hard && git clean -fd, run inside the worktree only.
  3. Add --disallowedTools "Bash(rm *)" and --max-turns, then do one test run.

If your job uses subagents or forked skills:

  1. Assume rewind won't restore their edits, and run the job in a worktree.
  2. Save the session ID and first UUID anyway (Pattern 2), so the main agent's own edits can still be rewound.
  3. After a test run, compare git diff with the rewind result to see what the subagents changed.

If you'd rather not build this yourself: a freelance developer who knows the Agent SDK and your CI can usually set up a worktree, validation and revert script for you. You pay for their time on top of your usual Claude usage. Ask for a fixed-scope quote, because rates vary too much to give a reliable figure here. This suits you if you have one important recurring job and little time. It's a poor fit if your jobs change every week, because you'll keep paying for changes. Another route is to run the job in GitHub Actions or GitLab CI with Claude Code (GitHub Actions docs, GitLab CI/CD docs). Every run then starts from a clean checkout, and you undo it by not merging its branch, so you don't depend on checkpoints at all.

Related on Headless Notes: look for our guides on running claude -p on a schedule, choosing permission modes for unattended runs, and using git worktrees for agent jobs. They cover the layers that this article treats as "use git instead."

Sources

Checked on 2026-10-15.