The Problem
You hit Claude's rate limit in the middle of a hard problem. You switch to Codex. You spend the next 20 minutes re-explaining the same architecture you spent two hours getting Claude to understand. The next day Claude is back, but now you're in Codex's flow — and switching back means re-explaining again, in the other direction.
Every coding CLI keeps its session on your local disk. The conversations are right there — but three formats, three private silos, no interop. The reason switching tools loses your context isn't a fundamental limitation — it's that nobody bothered to write the converter.
We did.
- Claude Code — ~/.claude/projects/<encoded-cwd>/<uuid>.jsonl
- Codex CLI — ~/.codex/sessions/<Y>/<M>/<D>/rollout-<ts>-<uuid>.jsonl
- Gemini CLI — ~/.gemini/tmp/<project>/chats/session-*.json
Meet authsec-bridge
authsec-bridge is a small CLI that reads your Claude Code / Codex / Gemini session from disk, transforms it into the target CLI's exact session schema, and drops it into the target's session folder. Then the target's native --resume flow finds the conversation as if you'd had it there all along.
Run this from the authsec-bridge directory, pointing --cwd at the project where your session was started:
python -m session_bridge.cli transfer --from claude --to codex --cwd "C:/Users/YourName/path/to/your-project"The command prints the new session ID and the exact resume command. Then resume in the target CLI:
codex resume <session-id>
gemini --resume <session-id>
# Claude: use /resume inside Claude CodeCodex (or Gemini) opens. Ask it 'summarize what we discussed' — it answers with the exact context from the Claude session. Same project, same decisions, same tradeoffs. Conversation continues like Claude handed the other tool the baton.
That's the entire product. ~600 lines of Python. No SaaS, no auth server, no sync daemon. Local files in, local files out.
The Architecture
The whole thing is a parser → neutral format → writer pipeline. Each CLI gets its own parser and its own writer. The contract between them is a tiny NeutralSession dataclass — an ordered list of user/assistant turns plus prose summaries of any tool calls.
The pipeline:
~/.claude/.../session.jsonl ─┐
~/.codex/.../rollout-*.jsonl ─┼──► parser ─► NeutralSession ─► writer ─► fresh session
~/.gemini/.../session-*.json ─┘ in target CLIThe project layout:
src/session_bridge/
├── model.py # NeutralSession / NeutralTurn dataclasses
├── paths.py # session storage paths per CLI
├── discover.py # list / find sessions on disk
├── parsers/ # per-CLI → NeutralSession
│ ├── claude_parser.py
│ ├── codex_parser.py
│ └── gemini_parser.py
├── writers/ # NeutralSession → per-CLI
│ ├── claude_writer.py
│ ├── codex_writer.py
│ ├── gemini_writer.py
│ └── markdown_writer.py
└── cli.pyAdding support for a new CLI — Cursor, Copilot, anything that comes next — is one parser + one writer + a path entry.
Challenge 1 — Tool Calls Don't Transfer
If Claude ran Edit("foo.py", "old_string", "new_string") in the source session, that's a Claude-specific tool ID with Claude-specific argument shape. Codex doesn't have an Edit tool — it has apply_patch. Try to inject Claude's tool_use record into Codex's parser and it crashes the loader with an exhaustive-switch error.
We strip them. Each tool invocation becomes a one-line summary attached to the assistant turn that produced it:
_(actions taken in the previous session:)_
- edited services/email/sender.py:82-94
- ran `pytest -k linkedin`
- read services/llm/prompts.pyThe new model sees what was done in narrative form. It can't undo those actions, but it understands them. That's the right trade-off for a handoff — you're switching tools, not state.
Challenge 2 — Each CLI Has Its Own Resume Registration
Writing the rollout file isn't enough. The CLI's resume picker reads from a separate index, and each CLI does it differently.
Half the work was reverse-engineering each CLI's registration ritual by reading its on-disk files and SQLite tables. Nothing about any of this is in any CLI's docs.
- Codex ≤0.122 — uses ~/.codex/session_index.jsonl. Append a JSON line, done.
- Codex ≥0.128 — moved to a SQLite database (~/.codex/state_5.sqlite → threads). Have to insert a row with the right schema or the picker doesn't show it.
- Gemini — uses both an autosave file (what gemini --resume <id> reads) and a git-tagged saved-chat system (what /chat resume <name> reads). Two parallel mechanisms.
- Claude — writes session UUIDs into project folders named by an encoded working-directory path. Get the encoding wrong and the session file lives in a directory Claude never looks at.
Challenge 3 — Claude's Filename Encoding
Claude turns a working directory path into a folder name by replacing every colon, backslash, forward slash, and space with a hyphen. The drive letter case is preserved as-typed.
input: C:\Users\Ritam Kumarb Kundu\Desktop\foo
encoded: C--Users-Ritam-Kumarb-Kundu-Desktop-fooTwo lines of regex once you know the rule. The hour you spend wondering why the tool can't find sessions for a project that's clearly there — that's where the real cost is.
What Else Got Built
A few things that came out of using it daily and noticing rough edges.
List recent sessions to find a specific one to transfer:
python -m session_bridge.cli list --from claude --limit 5
python -m session_bridge.cli list --from codex
python -m session_bridge.cli list # all CLIsTransfer a specific session (not just the latest) by copying the session ID from the list output:
python -m session_bridge.cli transfer --from claude --to gemini --session <SESSION-ID>Universal Markdown fallback — when the target CLI's resume mechanism is too brittle to fake, drop a HANDOFF.md and let any CLI read it:
python -m session_bridge.cli transfer --from claude --to markdownThen open any CLI and run:
read HANDOFF.md and continue from where the user last spokeWorks in Claude, Codex, Gemini, Cursor, Copilot — anything that reads project markdown. Lower-tech, higher reliability.
No synthetic signatures — the bridged session opens with a small banner so the new model knows the conversation came from another CLI, without pretending to be the original:
[session-bridge] This conversation was bridged from another CLI.
Tool calls from the previous CLI are described in prose below.
Continue from where the user last spoke; do not try to re-run those tool calls.What We Learned
Four lessons from building and using this daily.
- Strip tool calls. Don't try to be clever. The first version tried to map Claude's Edit to Codex's apply_patch. It worked sometimes and broke spectacularly other times. The model on the receiving end doesn't need to replay actions — it needs to understand them. Prose summaries are enough.
- Use the receiving CLI's exact schema. We initially wrote Gemini files with "type": "model" for assistant turns — that's the OpenAI convention. Gemini's loader has an exhaustive switch on type and the actual accepted value is "gemini". Crash trace looked like an internal React error; the fix was one string.
- The hardest CLI to support is the one that just shipped a new version. Codex 0.128 silently moved its session picker from JSONL to SQLite mid-development. The parser/writer abstraction is what keeps each fix isolated to one file.
- Local-only is the right scope. We were tempted to add a sync server and an account system. We deliberately didn't. The session files already live on your disk. Adding a network layer multiplies the surface area for security, latency, and trust by 10x for no real user benefit.
What's Next
A few known rough edges and what's coming:
- Codex 0.128+ picker visibility — bridged sessions land in the SQLite threads table, but a picker filter hides them from codex resume --all. Workaround: resume by direct UUID. Real fix: read Codex's open-source TUI source.
- Gemini git-tag integration — so /chat resume <tag> works in addition to gemini --resume <id>.
- LLM summarization for long sessions — a 300-turn session bridges as 300 turns. An optional --summarize flag that compresses old turns before bridging would help.
- Cursor and Copilot support — same pattern: parser + writer + path entry.
Try It
The project is open source. Install it:
git clone https://github.com/authsec-ai/authsec-bridge.git
cd authsec-bridge
python -m pip install -e .Verify the install:
python -m session_bridge.cli --helpTransfer the latest Claude session in any project into Codex or Gemini:
# Claude → Gemini
python -m session_bridge.cli transfer --from claude --to gemini --cwd "C:/Users/YourName/path/to/your-project"
# Claude → Codex
python -m session_bridge.cli transfer --from claude --to codex --cwd "C:/Users/YourName/path/to/your-project"
# Codex → Claude
python -m session_bridge.cli transfer --from codex --to claude --cwd "C:/Users/YourName/path/to/your-project"Then resume with the printed session ID:
codex resume <session-id>
gemini --resume <session-id>
# Claude: use /resume inside Claude CodeLinux, macOS, and Windows all work. The README has a compatibility table for what's solid and what's still rough.
If your CLI version is newer than what we tested against, the parser/writer pattern makes adding support a small PR.
Why This Matters Beyond the Immediate Utility
The CLI-coding ecosystem is in a weird state. Claude Code, Codex, Gemini, Cursor, Copilot — all do similar things, all charge separately, all have rate limits that hit at different times. Most users of one are users of two or three. Switching is the default experience, not the exception.
But every tool stores your work in a private silo as if you're going to use only it forever. That's not how anyone actually works. We rotate based on which model is best at the task, which one isn't rate-limiting today, which one our employer paid for this week.
A small interop layer says: your conversation isn't owned by your tool. It lives on your disk. Any other tool can read it.
That restores the invariant that your context is yours — not Anthropic's, not OpenAI's, not Google's. Yours.
authsec-bridge is part of the AuthSec platform — enterprise identity and access management for AI applications.


