How I keep Claude Code and Codex in sync
Table of contents
I use both Claude Code and Codex regularly and need my working environment to remain consistent regardless of which one I launch. When I teach Claude a workflow through a skill, add a safety hook, or modify my global instructions, Codex should automatically learn the same thing, and vice versa.
The hard part is that the two tools are not configured the same way. They have different instruction files, settings formats, hook events, hook payloads, permission systems, and skill metadata. A naive “just symlink everything” approach breaks as soon as one tool writes to a mutable settings file or expects a slightly different schema.
A tempting alternative is to keep a third, agent-agnostic source file and generate the Claude and Codex files from it. That approach breaks just as quickly. The shared behavior is not a clean abstract schema with two renderers. The differences fall into three kinds:
- Operational. Claude stores hook registration and many preferences in
~/.claude/settings.json, a live file that rewrites itself, while Codex exposes stable activation files such as~/.codex/hooks.jsonand~/.codex/config.tomlthat can be tracked through symlinks. - Semantic. The two tools expose different hook events.
- Human-facing. I want to read and edit the actual files the tools consume, not debug a generator when a workflow breaks.
A generated neutral layer would add a third configuration language without eliminating the real tool-specific cases.
The approach that does work is different. It consists in using the AI models themselves to create parallel versions of the relevant files for each tool. Hooks and a manifest then make drift visible and hard to ignore. Specifically, the system:
- Keeps the durable configuration for both tools in one version-controlled dotfiles repository.
- Treats Claude and Codex instructions, skills, and hook scripts as peer implementations of shared behavior, with explicit counterpart files wherever the tools need different syntax, discovery rules, or hook payloads.
- Uses hooks, a manifest, and a deterministic audit to make drift visible before it becomes committed state.
The layout
To reproduce the setup, start with a single version-controlled directory for the durable agent configuration. The directory can live wherever you keep dotfiles. The important part is to keep the synchronized Claude and Codex artifacts next to each other:
agent-config/
|-- claude/
| |-- CLAUDE.md
| |-- hooks/
| `-- skills/
|-- codex/
| |-- AGENTS.md
| |-- hooks/
| `-- skills/
|-- ai-config-sync.json
`-- bin/ai-config-sync
The files under claude/ and codex/ are the peer files I keep synchronized: CLAUDE.md with AGENTS.md, each Claude skill with its Codex counterpart, and each hook script with its matching hook script when both tools expose a suitable event.1
Then connect the files that the tools actually load to this tracked directory. For Claude, the stable global files are the instruction file and the skills directory:
ln -s "$AGENT_CONFIG/claude/CLAUDE.md" ~/.claude/CLAUDE.md
ln -s "$AGENT_CONFIG/claude/skills" ~/.claude/skills
Claude’s ~/.claude/settings.json stays a real file. The Claude hook registration lives in that file, but the commands inside it point at scripts under $AGENT_CONFIG/claude/hooks/. This is why there is no tracked claude/hooks.json or claude/config.toml equivalent.
For Codex, the global files are stable enough to symlink directly:
ln -s "$AGENT_CONFIG/codex/AGENTS.md" ~/.codex/AGENTS.md
ln -s "$AGENT_CONFIG/codex/config.toml" ~/.codex/config.toml
ln -s "$AGENT_CONFIG/codex/hooks.json" ~/.codex/hooks.json
ln -s "$AGENT_CONFIG/codex/rules" ~/.codex/rules
ln -s "$AGENT_CONFIG/codex/skills" ~/.codex/skills
Project-local configuration uses the same pairing pattern inside the project that needs it:
some-project/
|-- CLAUDE.md
|-- AGENTS.md
|-- .claude/
| |-- settings.json
| |-- hooks/
| `-- skills/
`-- .codex/
|-- hooks.json
|-- hooks/
`-- skills/
Those files are local to some-project. They should not be symlink targets for global ~/.claude/ or ~/.codex/ files. But when a project has local instructions, skills, or hooks, I keep the Claude and Codex sides paired: CLAUDE.md with AGENTS.md, .claude/skills/NAME/ with .codex/skills/NAME/, .claude/hooks/HOOK with .codex/hooks/HOOK, and .claude/settings.json with .codex/hooks.json for hook registration.
What counts as synchronized
There are three main classes of shared behavior: instructions, skills, and hooks. There is also an activation state, but I treat that as wiring rather than as a fourth peer content category.
Instructions. Claude instruction files are named CLAUDE.md. Codex instruction files are named AGENTS.md. At the global level, Claude reads claude/CLAUDE.md through ~/.claude/CLAUDE.md, while Codex reads codex/AGENTS.md through ~/.codex/AGENTS.md. At the project level, every project-local CLAUDE.md gets a sibling AGENTS.md counterpart. I used to configure Codex to treat CLAUDE.md as a project-doc fallback, but that made every project instruction file implicitly dual-use. That was convenient until a project needed Claude-specific syntax or Claude-specific operational detail. I now keep parallel instruction files throughout. The audit and commit guard normalize tool-specific forms, such as standalone Claude @file imports versus Codex-safe path references, before checking that paired instruction files say the same thing.
Skills. A reusable global skill should normally exist in both claude/skills/NAME/ and codex/skills/NAME/. Project-local skills follow the same rule inside the relevant project: .claude/skills/NAME/ pairs with .codex/skills/NAME/. The two skill bodies and auxiliary files should be equivalent. The audit-and-commit guard removes tool-specific frontmatter keys, such as allowed-tools, argument-hint, model, user-invocable, and argument metadata, from both sides before comparison. Runtime output directories such as generated digests, last-run files, and caches are intentionally ignored.
Hooks. Hooks are paired when the two tools expose compatible events and payloads. Global Claude hook scripts live in claude/hooks/ and are registered in the mutable live ~/.claude/settings.json. Global Codex hook scripts live in codex/hooks/ and are registered in the tracked codex/hooks.json file, which is live through ~/.codex/hooks.json. Project-local hook scripts follow the same convention inside a project: .claude/hooks/HOOK pairs with .codex/hooks/HOOK, and local hook registration pairs .claude/settings.json with .codex/hooks.json.2
The activation state is the wiring that brings those three categories to life. It is not enough for codex/hooks.json to exist in the repository: ~/.codex/hooks.json must point to it. It is not enough for the Codex skills to be paired: ~/.codex/skills must point at codex/skills/. The audit checks these global live links because otherwise the repository could appear correct while the agent reads stale local state.
Claude’s equivalent activation state is deliberately different. Its hook registration and global settings live in the mutable ~/.claude/settings.json file, so the tracked side is the hook scripts themselves, not the Claude settings file that registers them.
The manifest
The file ai-config-sync.json is the manifest for the global dotfiles arrangement. It records:
- paired instruction files
- every global skill pair
- hook pairing status
- live registration files
- runtime outputs that should be ignored
- Codex-only, Claude-only, unsupported, and intentionally divergent artifacts
The manifest is not a generator. It does not automatically rewrite one side from the other. When exact parity is possible, the manifest says which global files should match after tool-specific normalization. When parity is impossible or undesirable, the manifest says so explicitly.
Project-local instructions, skills, and hooks do not need to be listed in this global manifest. The sync script automatically treats the following as local pairs in whichever git repository the agent is editing or committing:
PROJECT/CLAUDE.mdandPROJECT/AGENTS.mdPROJECT/.claude/skills/andPROJECT/.codex/skills/PROJECT/.claude/hooks/andPROJECT/.codex/hooks/PROJECT/.claude/settings.jsonandPROJECT/.codex/hooks.json
That explicitness is important. Without it, a missing counterpart could mean “the agent forgot to pair this” or “this feature has no counterpart.” With the manifest, the difference has to be documented.
The two hook families
There are two hook families. One keeps the active agent aware of the counterpart immediately after an edit. The other blocks a commit when the agent is about to turn one-sided drift into repository history.
The first hook family is a reminder hook. After an agent edits a relevant file, the hook runs bin/ai-config-sync in reminder mode and adds context like:
claude/skills/foo changed; update codex/skills/foo in the same session.
On Claude, this is a PostToolUse hook wrapper around:
bin/ai-config-sync remind-claude
On Codex, it is the analogous wrapper around:
bin/ai-config-sync remind-codex
The second hook family is the enforcement hook. Before an agent runs a Git commit command, the require-ai-config-sync.sh hook calls:
bin/ai-config-sync guard-commit
The guard inspects staged files and common one-shot forms, including git add ... && git commit, git add -A, and git commit -a. It resolves the git repository targeted by the commit command, so the same guard works for global dotfiles pairs and for project-local CLAUDE.md=/=AGENTS.md, .claude/, and .codex/ pairs. For skills, it checks content parity after removing tool-specific front matter from both sides, rather than accepting any arbitrary file with the same name.
If the guard sees a Claude/Codex configuration change without the corresponding counterpart, it denies the tool call and tells the agent exactly what is missing. For global dotfiles artifacts, if a counterpart should not exist, the agent must update ai-config-sync.json with an artifact-specific exemption rather than relying on an implicit exception.
The hooks do not automatically edit the counterpart. They make counterpart synchronization part of the agent’s normal path to completion: update both sides, or explicitly document why a global counterpart should not exist.
The audit
There is also a deterministic audit:
bin/ai-config-sync audit
This audit verifies the manifest, topology invariants, live symlinks, global instruction equivalence, the dotfiles project instruction pair, global skill content after tool-specific frontmatter normalization, auxiliary skill files, hook file inventory, hook registrations, the Codex skills symlink, and Codex activation files. It does not edit anything. Its sole job is to make drift visible. The audit is intentionally global; most project-local pairs are checked by the reminder and commit-guard hooks when the agent edits or commits inside that project.
The topology checks are especially useful because they catch the confusing cases:
- a global Codex hook registration placed under
dotfiles/.codex/hooks.json - live
~/.claudeor~/.codexsymlinks resolving into project-local config - undocumented symlinks in tool homes
- dotfiles-project-local skills that should really be global reusable skills
The result is a three-layer guardrail system:
- live symlinks keep each tool reading the tracked files
- post-edit reminders keep the active agent aware of the counterpart
- pre-commit guards and the audit prevent accidental one-sided changes from becoming committed state
What this buys me
The practical result is that I can switch between Claude Code and Codex without having to maintain two mental models of my working environment. The same global rules about verification, secrets, Git hygiene, external actions, and project layout are available to both agents. The same reusable skills are discoverable by both. The same project-specific instructions, skills, and hooks can be paired locally as needed.
The deeper benefit is that the synchronization policy is explicit. It is not “these two directories should probably match.” It is a checked contract:
- these files are paired
- these files intentionally diverge
- these runtime outputs are ignored
- these live files must be symlinks
- these registration files must mention the expected hook scripts
- project-local sibling
CLAUDE.mdandAGENTS.mdfiles are peers - repo-root
.claude/and.codex/directories are project-local, not global
The important limitation is that this is an agent-facing system, not a general filesystem monitor. The reminders and commit guards run when Claude Code or Codex edits files or attempts commits. If I edit the same files manually or commit from an ordinary shell, those agent hooks do not fire. The global audit still provides a deterministic backstop for the dotfiles configuration, but project-local pairs are enforced only when the agent performs the edit or commit. That limitation is acceptable for my use case because these files are edited almost exclusively by agents.
Trying it yourself
To try the setup, give Claude Code or Codex this prompt:
Set up Claude Code <> Codex synchronization using https://github.com/benthamite/agent-sync-template.
- Clone the repository to a sensible local location if it is not already available.
- Run its smoke test before touching my live Claude or Codex configuration.
- Then follow BOOTSTRAP.md exactly.
The bootstrap is meant to be conservative: it runs the smoke test before touching live files, preserves existing mutable settings, and should stop to ask before overwriting, deleting, or merging anything it cannot handle safely. See the repo’s README.md for details.
Appendix: “semantic synchronization”
What this setup is doing resembles several older ideas in programming and software engineering. The closest terms I found are bidirectional programming,3 bidirectional synchronization,4 and multi-view consistency.5 All of these are concerned with the same general problem: the underlying information is represented in more than one artifact, and a change to one artifact should be propagated to the others so that the whole system remains consistent.
The traditional solutions are usually deterministic. One can designate a source file and generate the others from it, write converters between formats, or keep a neutral intermediate representation from which every concrete artifact is produced. Those approaches work well when the relation between the artifacts is clearly specified: Markdown to HTML, OpenAPI to client libraries, a schema to migrations, or a model to code.
The Claude/Codex setup is different because the relation between the artifacts is semantic rather than purely syntactic. A Claude skill and a Codex skill should implement the same workflow, but they may need different frontmatter, different activation rules, different hook payload handling, different path conventions, and different safety notes. The desired relation is not “these files are identical” or “this file is generated from that file.” It is closer to “these files express the same operational intent in the idiom of their respective tools.”
That is where capable AI models change the design space. Instead of writing a complete converter, one can maintain peer artifacts and ask the model to make the corresponding semantic edit on the other side. The deterministic parts of the system do not perform the translation; they make the translation governable. The manifest records which artifacts are paired or intentionally divergent. The reminders make the counterpart visible at edit time. The commit guard prevents accidental one-sided changes from becoming history. And the audit checks the parts that can be checked mechanically.
One may think of this pattern as agent-mediated semantic synchronization. It is a form of bidirectional synchronization, but with the consistency restorer implemented by an AI agent rather than by a hand-written transformation. The model handles the judgment-heavy part: deciding what the corresponding change should be in another format or tool. The surrounding software handles the boring but crucial part: finding the relevant artifacts, enforcing the workflow, making drift visible, and stopping before unsafe changes occur.
The same pattern seems applicable anywhere the relation between artifacts is too rich for a simple converter but structured enough to audit. Examples include keeping documentation and code aligned, keeping tests and specifications aligned, maintaining examples in several programming languages, synchronizing policy documents with operational runbooks, keeping configuration across tools in sync, or maintaining parallel integrations for different platforms.
With thanks to Alejandro Acelas for beta testing and to GPT-5.5 for copy editing.
Codex also has a few supporting global files that I keep in the same
codexdirectory:config.toml,hooks.json, and therulessubdirectory. These are activation files, not peer artifacts in the same sense.hooks.jsonregisters the Codex hook scripts.config.tomlholds Codex-wide settings; the parts relevant here are the hook feature flag and the project-doc fallback setting. Claude has analogous hook registration and settings, but they live inside~/.claude/settings.jsonand~/.claude/settings.local.json, which Claude mutates directly, so I leave them as real live files rather than symlinking them into version control. ↩︎Some hooks cannot be synchronized one-for-one. Claude has
Read,Grep,Glob, andSkillhook events that Codex does not expose in this setup. Claude Notification hooks also do not map directly to Codex; Codex lifecycle and notification-adjacent behavior goes throughcodex-hook-wrapperentries incodex/hooks.json. Chrome-specific Claude hooks have no Codex counterpart. These cases are recorded in thehookssection ofai-config-sync.jsonwith statuses such asclaude-only,codex-only, orunsupported, instead of being treated as unexplained drift. ↩︎Kazutaka Matsuda and Meng Wang, Applicative Bidirectional Programming: Mixing Lenses and Semantic Bidirectionalization, Journal of Functional Programming, vol. 28, 2018, pp. e15. ↩︎
Gábor Bergmann, Controllable and Decomposable Multidirectional Synchronizations, Software and Systems Modeling, vol. 20, no. 5, 2021, pp. 1735–1774. ↩︎
Alexander Knapp and Till Mossakowski, Multi-view Consistency in UML, arXiv, no. 1610.03960, 2016. ↩︎