DEV Community

Aaron W
Aaron W

Posted on

How Claude Code and Codex store sessions, and how to hand a task from one to the other

I pay for both Claude Pro and ChatGPT Plus and switch between Claude Code and Codex every day. Switching is easy. The handoff is the hard part: when Codex opens, it knows nothing about the project, how far you got, or which approaches already failed.

Here's how I made that handoff automatic, and what each side's sessions actually look like.

1. Claude Code sessions are local JSONL files

Every Claude Code session is a JSONL file under ~/.claude/projects/<escaped-project-path>/<session-id>.jsonl. One line per event: user messages, assistant replies, tool calls, tool results, todo updates.

So you can read a whole session without any API. The hard part is deciding how much to read:

  • Tool results can be huge (a full file read, a full test run). Paste them into another agent and you blow its context.
  • So you condense: keep messages verbatim, reduce each tool call to what was called, on what, and roughly what came back, then add the current todo list and git status.

2. Codex: use its own app-server

Codex keeps local session records too, but I don't parse them. I only use the app-server that ships with Codex: create a thread, send a message to it, read it back. That way a Codex update that changes the file format doesn't break anything, and writes go through the official path.

3. Two-way sync with cursors

My first version was a one-shot export: Claude writes HANDOFF.md, Codex reads it. But when you switch back, Claude has no idea what Codex did, so you export again.

What works now:

  • One Claude session is paired with one Codex thread.
  • Each side keeps a cursor: the last event already synced.
  • When you switch, only the condensed progress after the cursor is sent.
  • On the Claude side, a UserPromptSubmit hook prepends Codex's new progress to your next message. Each update is delivered once, usually a few thousand tokens.

The two sides end up working on one task instead of running two unrelated chats.

4. When to switch: watch the limits

Most of my switches happen because Claude's 5-hour window ran out, so there's also a heads-up when either side passes 90%.

I don't scrape usage myself. I read what tools you may already run write out: CodexBar, ccusage, abtop, claude-hud, ccstatusline and a few others. Whichever is installed gets used, with no setup.

5. The tool

I packaged all of this as ccx, an open-source macOS menu bar app. Click ⇄ to hand off. Everything runs locally, with no account and no server.

https://github.com/aaronwong1999/ccx

Even if you never use it, the approach is easy to copy: read Claude's JSONL, condense tool output, keep cursors, send only the delta. The point is to make the handoff cheap. If it isn't, you won't bother switching, and you'll be back to waiting out the limit.

I'm @aaron0x10 on X if you want to compare setups.

Top comments (2)

Collapse
 
xm-yang profile image
XM Yang · SweetAI •

The limit-watch section is the part that actually lives in my workflow too. The real pain isn't hitting either quota alone — it's that each tool only shows its own meter. I'd glance at Codex and feel comfortable while my Claude 5-hour window was quietly decaying, or burn half a Claude window on an exploratory rabbit hole and have nothing left for the real edit.

One trick worth stealing: keep separate books for exploratory rounds vs. delivery rounds. Claude Code stores every session as local JSONL, and those assistant messages carry token usage — so a small script can total per-session spend after the fact. Just tag each session with a one-line prefix ('explore' vs 'deliver') in your scratch notes, and group by that later.

The rule that falls out: exploration goes to the window with the most headroom, delivery goes to the freshest window. And since you already read ccusage/CodexBar output instead of scraping, one extension to try: normalize both tools' remaining quota into a single number — minutes of productive headroom — and show it in the status bar. The switch decision becomes one glance instead of two tabs, which is exactly the friction ccx is removing.

Collapse
 
xm-yang profile image
XM Yang · SweetAI •

The cursor idea is the part I keep thinking about. My old handoff was also a HANDOFF.md file, and it had exactly the drift you describe — two chats slowly becoming two projects. Condensing with per-side cursors is the right fix: it turns the handoff from a dump into a diff.

One trick worth adding on the quota side: treat each tool's 5-hour window as a budget you allocate, not a deadline you hit. When Claude's window is half gone, I move the speculative work (spikes, refactors) to Codex early and keep Claude's remaining window for the stuff only it does well. That way the forced switch never lands mid-thought — it's already happened on your terms.