Last active
August 14, 2026 06:30
-
-
Save discountry/00ba94f9e8d5650ec96efe9bfb784c4b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================ | |
| # Claude Code <-> Codex native session handoff (bash / zsh) | |
| # | |
| # claude-to-codex | |
| # codex-to-claude | |
| # claude-to-codex "focus on the failing tests" # optional note | |
| # | |
| # The prompt tells the agent how to work, step by step; session | |
| # discovery is spelled out as runnable commands. | |
| # ============================================================ | |
| # ------------------------------------------------------------ | |
| # Prompt template. Placeholders substituted in _agent_handoff: | |
| # __SOURCE__ __TARGET__ __STORAGE__ __DISCOVERY__ __EXTRA__ | |
| # ------------------------------------------------------------ | |
| _agent_handoff_template() { | |
| cat <<'TEMPLATE' | |
| You are __TARGET__, resuming work that __SOURCE__ left unfinished in the current | |
| working directory. That directory is the entire project scope. | |
| ## 1. Inspect the workspace first | |
| Run: `pwd`, `git status -sb`, `git log --oneline -10`, `git diff --stat`, | |
| `git diff --cached --stat`. Keep the output for step 5. | |
| ## 2. Find candidate sessions | |
| __DISCOVERY__ | |
| ## 3. Pick one | |
| Newest first. Read only the last 20-40 messages of a candidate — the ending | |
| reveals its state cheaply. | |
| Unfinished (resume it): the last turn proposes a next step or lists TODOs; the | |
| transcript ends on a failing test/build or an unhandled error; a user request was | |
| never delivered; uncommitted files match what that session was editing. | |
| Finished (move to the next older one): closing summary with no open items; a | |
| one-off question or read-only exploration; its changes are committed and clean. | |
| Skip sessions whose cwd is a different project. | |
| State your pick in one line: file path, date, why it is unfinished. | |
| ## 4. Rebuild context | |
| Read the relevant part chronologically and extract only: the goal, decisions and | |
| constraints, files touched, approaches already tried and rejected, and the exact | |
| stopping point (last completed step, first unfinished step). | |
| Skip pasted file contents and stale tool output — read files from disk instead. | |
| ## 5. Reconcile with disk | |
| Disk is authoritative. Verify every file the session claims to have changed. | |
| Already applied → mark that step done. Conflict → follow disk. Otherwise trust | |
| __SOURCE__'s completed work: verify, don't redo. | |
| ## 6. Report, then work | |
| Output three lines only — what __SOURCE__ was doing / where it stopped / what you | |
| are doing next — then start immediately. Don't ask permission, don't ask for a | |
| task description or a session ID. Verify with tests or a build and carry the task | |
| to completion. | |
| ## Boundaries | |
| Stay in this directory and on this branch; no new branches or worktrees. | |
| Preserve uncommitted work: `reset`, `checkout --`, `stash`, `clean`, `revert` | |
| need explicit user approval. | |
| Read __SOURCE__'s session files directly; write no handoff artifacts | |
| (CLAUDE.md, AGENTS.md, .handoff.md). | |
| ## Stop conditions | |
| No session for this cwd → say so and stop. | |
| All candidates finished → name the newest, say so, stop. | |
| Unfinished but blocked on a decision only the user can make → present the options | |
| and ask. | |
| ## User note | |
| __EXTRA__ | |
| TEMPLATE | |
| } | |
| # ------------------------------------------------------------ | |
| # Build the prompt and hand it to the target CLI | |
| # ------------------------------------------------------------ | |
| _agent_handoff() { | |
| local source_agent="$1" target_agent="$2" storage_dir="$3" target_cmd="$4" discovery="$5" | |
| shift 5 | |
| local extra="$*" | |
| [ -n "$extra" ] || extra="(none — just continue the unfinished task)" | |
| if ! command -v "$target_cmd" >/dev/null 2>&1; then | |
| printf 'error: command not found: %s\n' "$target_cmd" >&2 | |
| return 127 | |
| fi | |
| if [ ! -d "$storage_dir" ]; then | |
| printf 'warning: no %s session storage at %s\n' "$source_agent" "$storage_dir" >&2 | |
| fi | |
| local prompt | |
| prompt="$(_agent_handoff_template)" | |
| prompt="${prompt//__DISCOVERY__/$discovery}" # expand block first | |
| prompt="${prompt//__STORAGE__/$storage_dir}" # then paths inside it | |
| prompt="${prompt//__SOURCE__/$source_agent}" | |
| prompt="${prompt//__TARGET__/$target_agent}" | |
| prompt="${prompt//__EXTRA__/$extra}" | |
| printf '→ %s handing off to %s [%s]\n' "$source_agent" "$target_agent" "$PWD" >&2 | |
| command "$target_cmd" "$prompt" | |
| } | |
| # ------------------------------------------------------------ | |
| # Claude Code -> Codex | |
| # ------------------------------------------------------------ | |
| claude-to-codex() { | |
| local discovery | |
| discovery=$(cat <<'DISCOVERY' | |
| JSONL, one file per session, grouped by project: | |
| `__STORAGE__/projects/<slug>/<session-uuid>.jsonl`, where <slug> is the absolute | |
| cwd with every non-alphanumeric character replaced by `-`. | |
| ``` | |
| slug=$(pwd | sed 's/[^a-zA-Z0-9]/-/g') | |
| ls -t "__STORAGE__/projects/$slug"/*.jsonl 2>/dev/null | head -20 | |
| ``` | |
| If that directory is missing, the slug rule differs for this path — fall back to | |
| a content search, then sort the hits with `ls -t`: | |
| ``` | |
| grep -rlF "\"cwd\":\"$(pwd)\"" --include='*.jsonl' "__STORAGE__/projects" | head -20 | |
| ``` | |
| Confirm the schema before parsing: `head -1 FILE | jq .` | |
| Lines carry `.type` (user|assistant), `.timestamp`, `.cwd`, `.message.content`. | |
| Cheap tail read (adjust the filter if the schema differs): | |
| ``` | |
| jq -r 'select(.type=="user" or .type=="assistant") | |
| | "\(.timestamp) \(.type): \(((.message.content | |
| | if type=="array" then map(.text // "") | join(" ") else tostring end))[0:400])"' \ | |
| FILE | tail -40 | |
| ``` | |
| DISCOVERY | |
| ) | |
| _agent_handoff "Claude Code" "Codex" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" "codex" "$discovery" "$@" | |
| } | |
| # ------------------------------------------------------------ | |
| # Codex -> Claude Code | |
| # ------------------------------------------------------------ | |
| codex-to-claude() { | |
| local discovery | |
| discovery=$(cat <<'DISCOVERY' | |
| JSONL rollouts stored by date, NOT by project: | |
| `__STORAGE__/sessions/YYYY/MM/DD/rollout-<timestamp>-<uuid>.jsonl` | |
| So filter by the cwd recorded inside each file (in the `session_meta` record near | |
| the top); make sure you match the `cwd` field itself, not a path mentioned in the | |
| conversation body. | |
| ``` | |
| for f in $(ls -t "__STORAGE__/sessions"/*/*/*/rollout-*.jsonl 2>/dev/null | head -30); do | |
| head -5 "$f" | grep -qF "$(pwd)" && echo "$f" | |
| done | head -20 | |
| ``` | |
| Confirm the schema before parsing: `head -1 FILE | jq .` | |
| Records are wrapped: `.timestamp`, `.type` (session_meta|response_item|event_msg), | |
| `.payload` with `.role` and `.content[].text`. Older versions are flatter. | |
| Cheap tail read: | |
| ``` | |
| jq -r 'select(.payload.type=="message") | |
| | "\(.timestamp) \(.payload.role): \(((.payload.content | map(.text // "") | join(" ")))[0:400])"' \ | |
| FILE | tail -40 | |
| ``` | |
| To recall recent prompts fast, `__STORAGE__/history.jsonl` is a flat log: | |
| `tail -40 "__STORAGE__/history.jsonl" | jq -r '"\(.ts) \(.session_id): \(.text[0:200])"'` | |
| DISCOVERY | |
| ) | |
| _agent_handoff "Codex" "Claude Code" "${CODEX_HOME:-$HOME/.codex}" "claude" "$discovery" "$@" | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment