Codex can move a conversation back to an earlier prompt. The missing half is the workspace: the old conversation can return while the newer files remain on disk.
This guide shows how to add synchronized conversation-and-file rewind to Codex CLI now. The first section is the practical setup. The rest is a Rust-level look at the snapshot design: how it covers Git-tracked files, files directly edited by the agent, and a bounded set of recent shell-made changes without taking an unbounded snapshot of the entire workspace.
Disclosure: I built codex-rewind, the project used in this article. It is an unofficial distribution of OpenAI Codex CLI and is not affiliated with or supported by OpenAI. I am describing the implementation and its limits, not claiming that it is the best agent for every workflow.
The two-minute setup
Install codex-rewind from npm:
npm install -g codex-rewind
The executable is codexr, so it can live beside the official codex command rather than overwriting it. Start a new tracked session with:
codexr --enable file_snapshots
Use Codex normally. When an experiment goes in the wrong direction, run:
/rewind # choose an earlier prompt; restore the conversation and its files
/redo # undo that rewind and return to the pre-restore state
The important word is together. Restoring only the files leaves the model with a conversation that describes edits which no longer exist. Restoring only the conversation leaves the model reasoning about an older world while newer files remain on disk. The selected turn is the shared coordinate for both histories.
To enable tracking for future new sessions, add this to the existing Codex config:
# ~/.codex/config.toml
[features]
file_snapshots = true
Tracking is session-scoped. A session starts with snapshot tracking for its whole life, or it has none. Turning the feature on after the mistake cannot create snapshots for earlier turns.
A small test outside Git
You can test the behavior in an ordinary directory; no repository is required:
mkdir rewind-demo
cd rewind-demo
printf 'an uncommitted idea\n' > local-notes.md
codexr --enable file_snapshots
Ask the agent to delete local-notes.md, then use /rewind and select the prompt before the deletion. The file and the conversation should return to that point. /redo should move both forward again.
That example is deliberately not a Git trick. A useful agent-level undo buffer has to work for an untracked note, a generated document, or a directory that was never a repository.
The actual failure mode: two histories drift apart
There are two relevant states in an agent session:
- The transcript that determines what the model believes has happened.
- The workspace that contains the effects of what actually happened.
Codex CLI users have asked for a unified restore in open issues #9203 and #11626. The latter describes the gap precisely: conversation rewind exists, but the code changes after the selected point remain in the working tree.
Git is still the right tool for durable, reviewed project history. It is not a complete substitute for a per-turn safety net:
- A never-added file has no committed version to recover.
- Agent work increasingly includes notes, documents and data outside repositories.
- Committing or stashing every exploratory turn mixes disposable agent history with intentional project history.
- Shell commands and MCP tools can change files without producing a structured edit record that the model can later replay in reverse.
The target is therefore not “replace Git.” It is “make every restore point carry a coherent conversation state and a reversible workspace state.”
Boundary one: own the snapshot storage, not the repository
codex-rewind stores content-addressed blobs, manifests and per-thread references under Codex's own home directory:
~/.codex/file_snapshots/
├── blobs/
├── manifests/
└── refs/
The snapshot crate has no dependency on Codex's other crates and does not invoke Git or write to .git. Restore writes workspace files from its own store. The Git-tracked partition may read the index to obtain project-owned file paths, but snapshot objects, refs, commits and index mutations never enter the user's repository.
That separation is about ownership and compatibility. Session history belongs to the agent; repository history belongs to the user and their tools.
It also keeps the official Codex session format untouched. The existing rollout file gets no new fields or event types. A sidecar maps Codex turn IDs to snapshot manifest IDs:
Codex rollout: ... turn_id = "turn-42" ...
Snapshot sidecar: "turn-42" -> "manifest-b3..."
As a result, the same conversation can be opened by codex or codexr without conversion. That is format compatibility, not magical snapshot coverage: turns run in the official build do not create sidecar snapshots, so reopening that session in codexr cannot retroactively make those turns file-rewindable.
Boundary two: track a bounded union, not the whole tree
Separating storage from .git does not answer how many workspace files should be observed. That is a different problem.
On the repository used to develop this subsystem, a full subtree walk saw 70,609 files and about 116 GB. Most of that was build output, logs and caches that would not normally enter Git. The bounded tracked union was about 6,096 files and 59 MB.
Those numbers do not argue that Git is bad. They demonstrate why capture scope cannot be “everything below the working directory.” A build can keep inflating that tree even when the actual project has not changed.
The implementation unions three partitions:
| Partition | What it covers | What bounds it |
|---|---|---|
| Git-tracked | Files the project already identifies as source | The project index |
| Agent-touched | Files directly edited by the agent in this session | The agent's recorded actions |
| Recently modified residue | Shell/MCP changes outside the first two sets | 100 extra files, 16 MiB each, with churn directories skipped |
In Rust, the ordering is load-bearing. Recent files are selected last so their small budget is not wasted on paths the first two partitions already cover:
pub fn tracked_files(
roots: &[PathBuf],
already_known: impl IntoIterator<Item = PathBuf>,
include_hidden: bool,
) -> BTreeSet<PathBuf> {
let ignores: Vec<Gitignore> = roots.iter().map(|root| load_ignore(root)).collect();
// Partition 1 in this function: files the agent already touched.
let mut files: BTreeSet<PathBuf> = already_known.into_iter().collect();
// Partition 2: project-owned paths read from the Git index.
for (root, ignore) in roots.iter().zip(&ignores) {
files.extend(git_tracked_files(root, ignore));
}
// Partition 3: bounded residue not already covered above.
for (root, ignore) in roots.iter().zip(&ignores) {
files.extend(recent_files(root, ignore, include_hidden, &files));
}
files
}
The names “partition 1” and “partition 2” in that annotated excerpt describe program order; conceptually I usually list Git-tracked first because it is easier to explain. The result is a set union either way.
Only the recency partition needs arbitrary hard limits:
pub const RECENT_MAX_FILE_BYTES: u64 = 16 * 1024 * 1024;
pub const RECENT_LIMIT: usize = 100;
// After filtering ignored, covered, oversized and churn-directory entries:
candidates.sort_by_key(|(modified, _)| std::cmp::Reverse(*modified));
candidates.truncate(RECENT_LIMIT);
The other two partitions are bounded by meaning rather than a numeric cutoff. A Git-tracked file is part of the project even if it is large. An agent-touched file is relevant because the session deliberately changed it. Silently dropping the 101st agent edit would make the most important partition unreliable.
The 116 GB measurement is the observation that produced those engineering trade-offs: use all three partitions to broaden coverage, but never let ambient build churn decide the size of every checkpoint.
A separate ignore file is a feature, not duplicated configuration
Rewind and version control answer different policy questions:
-
.gitignore: should this path enter shared repository history? -
.codexsnapignore: may the rewind system capture and later touch this path?
Those sets should be allowed to overlap, but neither should imply the other. For example, local-notes.md may be intentionally excluded from Git while still being valuable enough to rewind. A database, credential file or generated cache may be excluded from snapshots regardless of the Git policy.
# .codexsnapignore
.env
data/*.sqlite
tmp/
The matcher uses familiar Gitignore syntax but loads only the rewind-specific file:
pub const SNAPSHOT_IGNORE_FILENAME: &str = ".codexsnapignore";
pub fn load_ignore(root: &Path) -> Gitignore {
let mut builder = GitignoreBuilder::new(root);
builder.add(root.join(SNAPSHOT_IGNORE_FILENAME));
builder.build().unwrap_or_else(|_| Gitignore::empty())
}
pub fn is_ignored(ignore: &Gitignore, path: &Path) -> bool {
ignore.matched_path_or_any_parents(path, false).is_ignore()
}
The rule is symmetric: an ignored path is never captured, restored or deleted by a restore. Applying it only during capture would create a dangerous asymmetry where an excluded file could still be removed later.
Safe restore requires evidence, then an escape hatch
A bounded snapshot cannot honestly claim that every absent path should be deleted. Absence may mean “did not exist,” or it may mean “outside the tracked set.” Confusing the two is how a restore becomes a deletion bug.
The restore planner therefore deletes only from witnessed history: the subsystem must have observed enough of a path's lifecycle to know that the target checkpoint says it was absent. It does not infer deletion from a partial directory listing.
Before applying any restore, it also checkpoints the current state. /redo restores that safety manifest. This changes the cost of a mistaken choice from lost work to an extra round trip.
There are still real limitations:
- A file no checkpoint ever saw cannot be restored.
- A file outside the Git index, changed only by a shell command and pushed out of the 100-file recent window may be missed.
- Hidden files are skipped by default unless directly edited;
.gitremains excluded from capture. - Rewind cannot undo remote side effects such as a pushed branch, sent request or database operation.
- Two concurrent sessions can overwrite each other's workspace changes; this is not a merge system.
Stating those gaps is more useful than calling any snapshot system “complete.”
Why the core and app-server boundary matters
A feature implemented only in a terminal UI solves one surface and creates the next compatibility problem. Codex also has Desktop and IDE clients, so the reusable unit must live below any one interface.
Capture lives in the Rust core where turn boundaries and tool execution are visible. Restore is exposed through the app-server protocol. The protocol field is intentionally small:
/// Also restore tracked workspace files to their state at the fork point.
/// A safety checkpoint is recorded first, so the restore is reversible.
#[experimental("thread/fork.restoreFiles")]
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub restore_files: bool,
codex-rewind exposes the CLI/TUI user flow today. Desktop and IDE clients do not ship this implementation, but the core store and protocol path are structured so another Codex surface can reuse the same capture and restore semantics instead of inventing another checkpoint format.
This cross-project, cross-surface concern is easy to miss in a local UI patch. It is also why “it works in my terminal” is not enough evidence that a rewind architecture fits Codex as a whole.
How the mechanism compares with Claude Code and OpenCode
This is a comparison of boundaries, not a product leaderboard.
Claude Code's checkpoint documentation says it tracks changes made through its file-editing tools, but not files modified by Bash commands. That is a clean and predictable boundary. codex-rewind keeps an equivalent agent-touched set, then adds Git-tracked paths and the bounded recency residue to catch more shell and MCP changes. The benefit is broader write-path coverage; the cost is more scope machinery and an explicit bounded-coverage gap.
OpenCode's snapshot documentation says it uses an internal Git repository and warns that large repositories or many submodules can cause slow indexing and significant disk use. codex-rewind instead uses its own content-addressed store and a bounded tracked set. The benefit is no Git repository dependency and less coupling to ambient tree size; the cost is a custom store, custom garbage collection and no Git-native interoperability.
| System | Tracking boundary | Storage approach | Main trade-off relevant here |
|---|---|---|---|
| Claude Code | File-editing tools | Session checkpointing separate from Git | Bash-made changes are not tracked |
| OpenCode | Agent operations / project snapshot | Internal Git repository | Docs warn about indexing and disk use on large repos/submodules |
| codex-rewind | Git-tracked + agent-touched + bounded recent residue | Independent content-addressed sidecar | Broader but still deliberately incomplete coverage |
The specific reason to try codex-rewind is therefore narrow: you use Codex, need conversation-and-file rewind now, want coverage beyond direct edit tools, and do not want the undo history written into the project's Git state.
Quick answers to the compatibility questions
Does codex-rewind replace the official codex command?
No. It installs a separate codexr executable, so both can exist on the same machine. It is an unofficial distribution built from an upstream Codex baseline, not an extension loaded into the official binary.
Does rewind require a Git repository?
No. In a repository, the subsystem reads the index as one source of project file paths. In a non-repository directory that partition is empty, while agent-touched and recently modified files still provide coverage. It never needs commits or stashes and does not write Git state.
Is the conversation format merely “compatible,” or actually unchanged?
It is unchanged. The implementation adds no field or event to Codex's conversation rollout. File history lives in a separate sidecar keyed by existing turn IDs. The practical caveat is coverage, not serialization: the official binary can read the conversation but does not produce snapshot entries for the turns it runs.
Does the same feature work across projects and Codex surfaces?
The capture policy is based on session workspace roots, not one repository layout, so the same subsystem handles repositories and ordinary directories across projects. The user-facing implementation here is CLI/TUI. Its Rust core and app-server restore API are reusable by Desktop and IDE clients, but those official surfaces have not adopted this subsystem.
Is it a complete backup system?
No. It is a bounded, session-level undo buffer. Keep using Git and normal backups for durable history. The three partitions increase useful coverage without promising a full-tree snapshot, and the limits section above defines the remaining gaps.
Compatibility and the upstream path
The package follows upstream Codex releases and installs alongside the official CLI. It deliberately shares the normal ~/.codex directory so login, configuration and conversation history can carry across. If you alternate executables inside one conversation, remember that only codexr writes file snapshots; finish a tracked conversation in the executable that started it, or use a separate CODEX_HOME if you prefer total isolation.
As of August 21, 2026, the official Codex contribution guide says that external code contributions and pull requests are not accepted. It asks for issues, root-cause analysis and design discussion instead. This project is therefore a usable bridge and a concrete design experiment, not a promise that an upstream PR will be merged.
If you want to inspect or try it:
The fastest path remains:
npm install -g codex-rewind
codexr --enable file_snapshots
If you test it, the feedback I care about most is not “does undo sound useful?” It is where the tracking boundary surprises you: which real agent-made file change escaped the three partitions, or which captured path you believe should have stayed outside the rewind system.
AI-assistance disclosure: I used an AI coding assistant to help structure and edit this article. I reviewed the technical claims and code excerpts against the project source and the linked documentation before publication.






Top comments (0)