hc - relocate a coding-agent session across harnesses and resume it natively
hc # interactive wizard (TTY) hc --from HARNESS --to HARNESS [SESSION] [-y|--write] ... hc list --from HARNESS [-n N] # TTY: pick then convert
Your harness hit a rate limit 80% through the fix. It cannot even write you a handoff doc, because the thing that would write it is the thing that is down.
hc reads the session transcript straight off disk, rewrites it into the target harness's native format, and prints the exact resume command. The dead harness never needs to run again, and your quota never needs to come back. Two seconds, and you keep going where you left off.
By default hc is a dry run: it prints what it would do. Pass --write to create the destination session. Stdlib only; zero runtime dependencies. An escape hatch cannot ask you to resolve a dependency tree while you are on fire.
--from HARNESS
Source harness. One of claude, codex, opencode,
grok, cursor.
--to HARNESS
Destination harness. Writable set: claude, codex,
opencode, grok. cursor is source-only and rejected
here.
SESSION
Optional session id. Default: newest convertible session for the
source cwd (last write / last activity, harness-specific).
--write
Actually create the destination session. Without it, dry run only.
--cwd DIR
Source working directory used to locate sessions. Default: current
directory.
--dest-cwd DIR
Destination working directory written into the converted session.
Default: same as --cwd. Use this to relocate a session across
folders inside one harness (lossless metadata rewrite).
-n N, --n N
How many newest sessions to list or offer in a picker. Default: 10
(list) or 15 (convert pick).
-y, --yes
Write without asking. Same as answering yes to the TTY confirm.
--no-interactive
Never prompt. Also set HC_NO_INTERACTIVE=1. Pipes are
non-interactive automatically.
Your harness dies mid-fix:
> fixing the failing payments test... ✗ usage limit reached · resets in 1h (30 minutes to ship)
See what is recoverable:
$ hc list --from claude -n 5
claude sessions for /proj (newest first, 5 shown)
ce0dc35e-... 2026-08-03 17:19 Fix the failing payments test
a1b2c3d4-... 2026-08-03 12:04 Review PR 1201
...
Dry-run the move, then write and resume:
$ hc --from claude --to codex from : claude (ce0dc35e-...) to : codex records: 243 (78 tool calls) dest : ~/.codex/sessions/.../rollout-...-ce0dc35e....jsonl (dry run; pass --write to create it) $ hc --from claude --to codex --write ... WROTE. resume with: cd /proj && codex resume ce0dc35e-...
Into Grok Build (session directory under ~/.grok):
$ hc --from claude --to grok --write ... WROTE. resume with: cd /proj && grok --resume <uuid>
Out of Cursor (read-only source) into Claude:
$ hc --from cursor --to claude --write
Into OpenCode (import file, not a live DB poke):
$ hc --from codex --to opencode --write ... WROTE. resume with: cd /proj && opencode import <file> && opencode -s ses_...
Common interface. Every harness maps to four records: user message, assistant reply, tool call, tool result. That floor guarantees any pair converts and resumes. Private reasoning is dropped (provider-owned / encrypted; unrecoverable).
N² enrichment. Surplus the floor cannot hold (session titles) rides a sparse (from, to) map layered on top. Missing pairs stay common-only. The map never re-encodes the four records, only the extras.
Dual streams. A session is model context plus UI scrollback plus identity. Writers emit whatever the destination needs so history paints in the TUI (Codex response_item + event_msg tool cards; Grok chat_history + updates; Claude's single row set serves both).
Ragged tails. The source usually died mid-tool-call. Every orphaned call gets a synthetic error result, or the resumed API rejects the history. That is the normal case for this tool, not an edge case.
claude Claude Code ~/.claude/projects R/W codex Codex ~/.codex/sessions R/W opencode OpenCode ~/.local/share/opencode R/W grok Grok Build ~/.grok/sessions ($GROK_HOME) R/W cursor Cursor ~/.cursor/chats read-only
Any direction among the writable ones. Converting into OpenCode writes an import file; resume with opencode import FILE then opencode -s ID. Converting into Grok writes a session directory; resume with grok --resume ID. Cursor has no import path, so --to cursor is refused. Within a harness, sessions also relocate freely across working directories.
Pick one. Python 3.10+; the npm package shells out to python3 on PATH.
From a checkout: pipx install . or python3 hc.py ...
GROK_HOME
Override Grok's base directory (default ~/.grok). Sessions are
under $GROK_HOME/sessions.
XDG_DATA_HOME
Affects OpenCode's store path when set
($XDG_DATA_HOME/opencode).
0 Success (including dry run and empty list with a message).
1 No matching session, unknown harness, or write refused
(read-only destination).
2 Usage / argparse error.
hc.agentlab.in/changelog · github.com/harshitsinghbhandari/harness-convert
MIT · built by Harshit Singh Bhandari · agentlab.in