-
Notifications
You must be signed in to change notification settings - Fork 4
Codex
One archive for Claude Code and Codex. From 1.1.0, Longhand captures Codex Desktop and Codex CLI threads (the "rollouts" under ~/.codex/sessions) into the same ~/.longhand archive the Claude Code hooks write. Codex sessions are namespaced codex:<thread-id>, so nothing collides, and a question asked in either client can be answered from work done in the other.
Current as of v1.2.0. The repo copy of this guide is docs/codex.md.
pip install -U longhand
longhand codex-sync # capture every Codex thread on this machine
longhand doctor # the "Codex capture" row confirms itThen connect the shared keyword server to each client:
# Claude Code — alongside the existing `longhand` server
claude mcp add --scope user longhand-shared -- longhand shared-mcp
# Codex CLI
codex mcp add longhand -- longhand shared-mcpCodex Desktop does not put codex on your PATH. Add the server to ~/.codex/config.toml instead and restart the app:
[mcp_servers.longhand]
command = "longhand"
args = ["shared-mcp"]
# Only if your archive is not at ~/.longhand:
[mcp_servers.longhand.env]
LONGHAND_DATA_DIR = "/Users/you/.longhand"Use an absolute path to longhand (which longhand) when the desktop app's PATH differs from your terminal's. If you relocated the archive with LONGHAND_DATA_DIR, set the same value for both clients and for the capture commands — one archive is the whole point.
Claude Code longhand server (13 semantic tools) |
longhand shared-mcp (4 keyword tools) |
|
|---|---|---|
| Lists and pages Codex sessions | yes | yes |
| Keyword search across both clients | no | yes (search, literal phrase) |
recall and semantic search over Codex sessions |
30 min after a thread quiets (1.2.0) | no |
| Commits made in Codex | yes (find_commits) |
through search
|
| Loads the embedding model | yes | never |
The shared server exposes list_sessions (with source="codex"|"claude" and project substring filters), search, get_session_timeline, and get_event_text. Search matches literal phrases, not meaning. It reads the same SQLite rows both clients write, never opens Chroma, and never loads a model; long texts and raw records page in 8,000-character slices. A broad query on a large archive can hit the server's instruction budget — narrow it to a session.
Capture runs in two passes, the Codex twin of Claude Code's Stop and SessionEnd hooks. A new or changed rollout is first captured exact-record-only (ingestion stage archived): every message, reasoning summary, tool call, and output is stored verbatim and keyword-searchable, with no vector model loaded — the same reason Claude's per-turn Stop hook skips embeddings. Codex sends no session-end signal, so quiet stands in for it: once a rollout has been untouched for 30 minutes (--finalize-after, in seconds) it gets the full pipeline — embeddings, episodes, project inference — and recall and semantic search see it (1.2.0). A finalized thread that resumes is captured exact-only again and finalized again once it settles: one re-embed per resume. longhand codex-sync --semantic runs the full pipeline on everything immediately, --no-finalize keeps a run exact-only, and analyze never embeds events, so it is not the remedy for an archived thread. doctor shows a "Codex finalizer" row while any thread is archived: green while they are still being written, yellow when one has been quiet past the bound and nothing has finalized it.
longhand reconcile --fix runs both passes — captures new or changed Codex rollouts and finalizes the quiet ones — along with everything it already does for Claude transcripts, so the scheduled reconciler (longhand schedule install-reconciler, every 30 minutes on macOS) keeps Codex current and recallable with no extra setup. For an immediate run use longhand codex-sync; for a foreground loop, longhand codex-sync --watch (every 60 seconds until interrupted). On a 60-second schedule a thread is recallable about 30 minutes after its last message; the poller loads the embedding model only on the run that has a quiet thread to finalize, one thread per run.
The repo ships scripts/com.longhand.codex-sync.plist.template, a user LaunchAgent that runs codex-sync at login and every 60 seconds. Fill in the interpreter that has Longhand installed and your home directory, then load it:
PY="$(command -v python3)" # must be the Python that has longhand installed
mkdir -p ~/.longhand/logs
sed "s|__PYTHON__|$PY|g; s|__HOME__|$HOME|g" \
scripts/com.longhand.codex-sync.plist.template \
> ~/Library/LaunchAgents/com.longhand.codex-sync.plist
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.longhand.codex-sync.plistThe job exits between scans, so state = not running with last exit code = 0 is normal. Inspect it with launchctl print "gui/$(id -u)/com.longhand.codex-sync" and tail -n 5 ~/.longhand/logs/codex-sync.log. launchd does not read your shell profile, so a relocated archive needs LONGHAND_DATA_DIR in the plist's EnvironmentVariables. To stop capture without deleting any archived history, launchctl bootout "gui/$(id -u)" the plist and remove it.
On Linux, run --watch in a persistent terminal or schedule longhand codex-sync with a systemd timer or cron. On Windows, --watch or Task Scheduler.
-
Threads you drove. Codex also spawns threads for itself — its "guardian" approval reviewer, for one — that re-quote the parent thread.
codex-syncskips them by default so every search hit appears once;--include-subagentscaptures them. -
Canonical items only. Codex writes every message twice: once as a canonical
response_itemand once as a UIevent_msgmirror. The mirrors, token accounting, and turn bookkeeping are skipped so nothing is stored twice. Reasoning is stored when Codex provides a readable summary; encrypted reasoning has no readable content and is skipped. -
Bounds. Per run: up to 50 sessions, each up to 16 MiB and 20,000 events. Larger rollouts are reported as
deferred, never partially imported. Raise the bounds with--limit,--max-file-kb, and--max-events. These are input bounds, not a memory ceiling — the default capture never loads a model. -
Drift is never silent. A record shape Longhand does not recognize is preserved as an
unknownevent with its raw JSON intact, and surfaces inlonghand doctor's "Transcript format" row asresponse_item/<kind>orevent_msg/<kind>.tests/fixtures/codex_shapes/regression-gates every known shape. -
Shell commands are understood; scripts are text. Commands run through Codex's shell tools, and the
cmd:literals inside itsexecscripts, feed error detection and git extraction, so commits made from Codex show up infind_commitsandlonghand git-log. Patches and the rest of a script stay recorded text — they are not translated into Claude-style file replay. - Redaction and locks apply. Opt-in secret redaction covers Codex records. Capture runs under the same ingest lock as the hooks; a Claude hook that fires while a capture holds the lock skips that turn, and the reconciler heals it.
This is shared, retrievable history — not a transfer of a model's live context. Only locally saved rollouts are available.
-
recallcan't see a Codex session — either the thread is still active (it is finalized 30 minutes after it goes quiet;longhand codex-sync --semanticindexes it now), or your Claude Code session was already open when it was finalized: the semantic server keeps the index it opened with, so restart Claude Code. The shared server's keywordsearchreads SQLite and finds it either way. -
doctorsays rollouts are "new or changed since capture" — runlonghand codex-sync;reconcile --fixalso captures on its schedule. -
A Codex thread is missing entirely — check
longhand codex-sync --dry-run:subagent: truemeans it is a thread Codex spawned for itself (skipped by default);within_size_limit: falsemeans it is over--max-file-kb. -
doctor's "Transcript format" row namesevent_msg/<kind>— Codex added a record kind Longhand has not dispositioned yet. It is preserved, not lost; please report the kind on the issue tracker.