codex2claude is a local one-way bridge from Codex to Claude.
It provides:
- a reusable Python CLI bridge
- Claude session persistence via native
session_id - deterministic per-thread locking
- a thin Codex skill wrapper surface
- no non-stdlib Python runtime dependency inside the bridge
Current implementation target:
- macOS / POSIX environments with Python 3 and local Claude CLI access
- not designed for Windows in its current
fcntl-based form
Implemented and locally verified on this machine:
askstatusforgetgcdoctor- automatic resume via stored Claude
session_id - same-thread lock conflict handling
Fresh verification completed during implementation:
PYTHONPATH=bridge python3 -m unittest discover -s tests -p 'test_*.py' -v- real Claude new-session smoke
- real Claude resume smoke
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -e .When a PyPI release is available, install with:
python3 -m pip install codex2claudeIf you do not want to install it yet, you can run it directly:
PYTHONPATH=bridge python3 -m codex2claude ask --prompt "Reply with ok only"After editable install, both of these work:
python -m codex2claude --help
codex2claude --helpAsk Claude in the current workspace thread:
codex2claude ask --prompt "Review this design" --workspace "$PWD"Use a named thread:
codex2claude ask --prompt "Continue the design review" --workspace "$PWD" --thread designForce a fresh Claude session:
codex2claude ask --prompt "Start over" --workspace "$PWD" --newAsk Claude and get structured output:
codex2claude ask --prompt "Review this design" --workspace "$PWD" --jsonInspect stored state:
codex2claude status --workspace "$PWD"Run diagnostics:
codex2claude doctor --workspace "$PWD"Forget the current thread:
codex2claude forget --workspace "$PWD"Remove stale thread files:
codex2claude gc --max-age-days 7The Codex-facing wrapper lives at:
skills/codex-to-claude/SKILL.md
The skill should stay thin. It should only:
- collect the user prompt
- choose default thread, named thread, or
--new - invoke
codex2claude - return stdout or surface stderr
It should not own Claude JSON parsing, session files, or retries.
Common trigger phrases that should explicitly steer Codex toward this skill:
Use the codex-to-claude skillask Claude about thissend this to Claudelet Claude review thisask ccask cc about thiscc review thiscc 怎么看cc 觉得呢cc 能帮忙看下吗问问cc问下ClaudeClaude 怎么看Claude 能看下吗让cc帮忙看看cc review下cc check一下give this to cc给 Claude 看看让 Claude 看一下发给 Claude给 cc 看看让 cc review 一下发给 cc
For stability, prefer phrases where cc appears with an action like ask, review, check, or look. Avoid relying on bare cc by itself.
Recommended everyday trigger phrases:
给cc看看这个问问cccc怎么看让cc review一下给Claude看看
More explicit variants:
Use the codex-to-claude skillask Claude about thisask cccc review this给 cc 看看问下Claude
These paths are confirmed to work:
- new Codex sessions started after the skill was installed
codex execruns started after the skill was installed- direct
codex2claudeCLI usage
Do not assume already-open Codex sessions will hot-reload newly installed or updated skills.
If you changed trigger phrases or installed the skill during an existing session, restart Codex or open a fresh session before testing.
If a trigger phrase does not route to Claude:
- Start a new Codex session.
- Test with a high-signal phrase such as
问问cc:请只回复 ok. - If needed, use the most explicit form:
Use the codex-to-claude skill. Ask Claude: ... - Verify the bridge directly with
codex2claude ask --prompt "Reply with ok only" --workspace "$PWD". - Run
codex2claude doctor --workspace "$PWD"to confirm the Claude CLI and thread-state diagnostics.
If direct CLI usage works but a natural-language trigger does not, the issue is skill discovery in that session, not the bridge itself.
By default, one workspace maps to one Claude thread.
Use --thread <name> to split conversations inside the same repo:
codex2claude ask --prompt "Review API design" --workspace "$PWD" --thread api
codex2claude ask --prompt "Review docs tone" --workspace "$PWD" --thread docsUse --new when you want a fresh Claude conversation for the selected thread key.
By default, ask now prefixes the visible reply with the resolved Claude model name:
[model: claude-opus-4-6[1m]] <reply text>
Use --json when you need structured output instead of the prefixed text form. The JSON shape is:
{
"model": "claude-opus-4-6[1m]",
"reply": "hello",
"session_id": "7f094c44-8dff-49dc-8830-70afe1135955"
}session_id may be null if Claude does not return a reusable session for that response.
Model resolution rules:
- prefer the first model key found in Claude's
modelUsage - fall back to
modelormodel_nameif present - use
unknownif the Claude payload does not expose model information
CODEX2CLAUDE_CLAUDE_BIN: override the Claude executable pathCODEX2CLAUDE_HOME: override the bridge state root without changing your real shellHOMECODEX2CLAUDE_RUN_REAL=1: enable opt-in real Claude integration tests
codex2claude doctor --workspace "$PWD" prints JSON and checks:
- the resolved bridge root and key paths
- whether the Claude CLI version can be read
- whether the selected thread state is present, missing, or corrupted
It returns 0 when required checks are healthy and non-zero when the Claude CLI check fails or thread state is corrupted.
Typical output shape:
{
"ok": true,
"bridge_version": "0.1.3",
"workspace_root": "/path/to/repo",
"bridge_root": {
"status": "ok",
"path": "/Users/you/.codex/codex2claude"
},
"paths": {
"threads": "/Users/you/.codex/codex2claude/threads",
"logs": "/Users/you/.codex/codex2claude/logs",
"state_file": "/Users/you/.codex/codex2claude/threads/<thread_key>.json"
},
"claude": {
"status": "ok",
"bin": "claude",
"version": "2.x.x (Claude Code)"
},
"thread_state": {
"status": "ok",
"path": "/Users/you/.codex/codex2claude/threads/<thread_key>.json",
"thread_key": "<sha256>",
"session_id": "<claude-session-id>",
"last_status": "ok",
"last_used_at": "2026-03-28T03:25:11Z"
}
}Field meanings:
ok: overall health for the selected workspace threadbridge_version: installed bridge version that produced the reportworkspace_root: canonical workspace path used for thread identitybridge_root.path: state root currently used by the bridgepaths.state_file: exact thread-state file for this workspace and thread nameclaude.status:okwhenclaude --versioncan be read, otherwiseerrorthread_state.status:ok,missing, orerror
Interpretation guide:
claude.status = okandthread_state.status = missing: healthy first-use stateclaude.status = okandthread_state.status = ok: healthy reusable thread stateclaude.status = error: Claude CLI is not reachable from this shellthread_state.status = error: the saved state file exists but could not be parsed or validated
Common fixes:
- Claude CLI unavailable:
Run
claude --versiondirectly. If that fails, fix your Claude CLI install or shellPATH. If you use a custom binary, setCODEX2CLAUDE_CLAUDE_BIN. - Unexpected bridge path:
Check whether
CODEX2CLAUDE_HOMEis set. If it is unset, the default state root is~/.codex/codex2claude. - Corrupted thread state:
Run
codex2claude forget --workspace "$PWD"and retryask. This removes the saved state for the selected thread key and allows a clean new session. - Wrong workspace identity:
workspace_rootis canonicalized before hashing. If you expected a different thread, verify the exact--workspacepath and any--thread <name>value you used.
Run the main test suite:
PYTHONPATH=bridge python3 -m unittest discover -s tests -p 'test_*.py' -vRun the opt-in real Claude smoke test:
PYTHONPATH=bridge CODEX2CLAUDE_RUN_REAL=1 python3 -m unittest tests.test_real_claude_cli -vThe real-Claude test is opt-in because it spends actual Claude usage and requires local auth.
0: success1: Claude invocation failure or generic bridge failure2: Claude timeout3: same-thread lock conflict4: invalid arguments5: corrupted state or persistence failure
Error-class mapping:
BridgeError: base error type, defaults to exit code1ClaudeInvocationError: Claude CLI not found, non-zero Claude exit, or generic Claude failure, exit code1ClaudeTimeoutError: Claude subprocess timeout, exit code2LockConflictError: same-thread lock conflict, exit code3InvalidArgumentsError: invalid CLI arguments or unsupported command path, exit code4StateCorruptionError: malformed Claude JSON, invalid state shape, or corrupted persisted state, exit code5
~/.codex/codex2claude/
threads/
runs/
logs/
Important files:
threads/<thread_key>.json: current thread state and stored Claudesession_idruns/<thread_key>/...json: per-run artifactslogs/bridge.log: append-only bridge events
Typical thread-state shape:
{
"thread_key": "<sha256>",
"workspace_root": "/path/to/repo",
"thread_name": null,
"claude_session_id": "<claude-session-id>",
"created_at": "2026-03-28T00:00:00Z",
"last_used_at": "2026-03-28T03:25:11Z",
"last_status": "ok",
"bridge_version": "0.1.3",
"claude_version": "2.x.x (Claude Code)",
"last_error": null
}Typical run-record shape:
{
"run_id": "<uuid>",
"thread_key": "<sha256>",
"started_at": "2026-03-28T03:25:10Z",
"ended_at": "2026-03-28T03:25:11Z",
"duration_ms": 842,
"used_resume": true,
"prompt_sha256": "<sha256>",
"exit_code": 0,
"parse_ok": true,
"stdout_preview": "Claude reply preview",
"stderr_preview": ""
}When the natural-language trigger path is the problem, separate skill discovery from bridge behavior:
- Verify the bridge first:
codex2claude ask --prompt "Reply with ok only" --workspace "$PWD" --new - Start a fresh Codex session before testing trigger changes.
- Use a high-signal trigger:
问问cc:请只回复 ok,不要输出别的内容。 - If that still fails, use the explicit form:
Use the codex-to-claude skill. Ask Claude: Reply with ok only. - Check
skills/codex-to-claude/SKILL.mdfor the exact trigger surface.
Important limitations:
- this repository does not provide a dedicated Codex skill-discovery log
logs/bridge.logonly records bridge activity aftercodex2claudeis actually invoked- if direct CLI usage works but the natural-language trigger does not, the failure is in session skill loading or skill routing, not in the bridge core
- after editing
SKILL.md, validate in a fresh Codex session; do not assume hot reload in an already-open session
- Why does resume fail after it worked earlier?
The saved Claude
session_idmay no longer be reusable, or the thread state may be corrupted. Runcodex2claude doctor --workspace "$PWD", thencodex2claude forget --workspace "$PWD"if you need a clean thread. - What should I do when I hit a lock conflict?
Another process is already using the same workspace/thread key. Wait for the other run to finish, or use a different
--thread <name>if you intentionally want a separate conversation. - Can multiple workspaces share one Claude thread? Not by default. Thread identity includes the canonical workspace path, so different workspaces intentionally map to different thread keys unless you build a different policy.
This version is intentionally one-way only:
- Codex initiates
- Claude replies
- bridge stores Claude
session_id - follow-up turns use native
claude --resume
Bidirectional agent protocols are out of scope for v1.
- Design:
docs/superpowers/specs/2026-03-28-codex-to-claude-design.md - Plan:
docs/superpowers/plans/2026-03-28-codex-to-claude-v1.md - Roadmap:
docs/roadmaps/2026-03-28-v0.2.0-roadmap.md - Agent Guide:
AGENTS.md - Architecture:
ARCHITECTURE.md - Development Handbook:
DEVELOPMENT.md
See CONTRIBUTING.md for development, verification, and release steps.
GitHub Actions runs unit tests and packaging checks on pushes and pull requests.
PyPI publishing is wired through .github/workflows/release.yml and currently uses PyPI Trusted Publishing from GitHub Actions for the main repository. If you fork this project, configure the matching trusted publisher entry on PyPI before expecting tag-based publishing to succeed in your fork.
For local packaging on Homebrew-managed Python, prefer a dedicated virtual environment for build and twine instead of installing them into the system interpreter.