English | 한국어
Hand off your agent setup between devices — sync ~/.claude/ or ~/.codex/ across machines.
If you use Claude Code or Codex on more than one machine — say a home Mac and a work Mac — moving hooks, skills, agents, rules, and local config between them is surprisingly painful. Copying files across often breaks because absolute paths like /Users/your-home-name/… don't exist on the other machine.
agent-handoff automates the whole thing. Per-machine paths are rewritten to portable tokens so configs resolve correctly wherever you pull them, a scanner catches secrets (API keys, tokens) before anything leaves your device, and a shared hub repository keeps every machine's configs and push history in one place.
Claude Code users can run it through slash commands. Codex users run the same handoff CLI directly with --profile codex; the Codex plugin bundles workflow guidance for Codex, not slash commands.
Inside Claude Code:
/plugin marketplace add im-ian/agent-handoff
/plugin install agent-handoff@agent-handoff
/reload-plugins
One-time terminal step (npm publish pending — see Installation):
git clone https://github.com/im-ian/agent-handoff.git && cd agent-handoff
npm install && npm run build && npm linkThen back in Claude Code:
/agent-handoff:init # asks you a few questions, creates a PRIVATE GitHub hub repo
/agent-handoff:push # snapshot ~/.claude/ to the hub
For Codex from the terminal:
handoff init --profile codex --hub git@github.com:you/agent-handoff-hub.git --device my-mac
handoff pushOptionally install the local Codex plugin so new Codex sessions can load the Agent Handoff skill:
codex plugin marketplace add /path/to/agent-handoff
codexThen open /plugins, install/enable Agent Handoff, restart Codex, and ask Codex in natural language, for example: "use agent-handoff to check status" or "push my Codex setup". The actual execution path is still the handoff CLI.
On another Claude Code machine — after the same install + /agent-handoff:init:
/agent-handoff:pull # pick the source device, preview diff, apply
Claude Code slash commands drive prompts through AskUserQuestion (device pickers, secret-scan policy, install confirmations). Codex uses CLI commands plus the bundled skill guidance.
/plugin marketplace add im-ian/agent-handoff
/plugin install agent-handoff@agent-handoff
/reload-plugins
Updates ride through /plugin update.
This repo also ships a Codex plugin at plugins/agent-handoff/. The repo-local marketplace entry lives at .agents/plugins/marketplace.json.
Codex v0.130 uses /plugins in the TUI. There is no /plugin marketplace add ... slash command. Add the marketplace from your terminal, then install/enable it from /plugins:
codex plugin marketplace add /path/to/agent-handoff
codexInside Codex:
/plugins
Pick Agent Handoff, install/enable it, then restart Codex so the bundled skill is available.
Important: Codex v0.130 plugin docs expose plugins as skills, apps, MCP servers, and hooks. Plugin commands/ files are not loaded as TUI slash commands, so /push, /agent-handoff:push, and similar entries will not appear in the / palette. Use the terminal CLI or ask Codex to use the Agent Handoff skill.
If you only need the sync functionality, the terminal CLI is enough:
handoff init --profile codex --hub git@github.com:you/agent-handoff-hub.git --device my-mac
handoff push
handoff pull --from other-macThe Claude Code plugin and Codex skill both rely on a handoff binary on your PATH. Until npm publish lands, install from source:
git clone https://github.com/im-ian/agent-handoff.git && cd agent-handoff
npm install && npm run build && npm linkVerify with /agent-handoff:status inside Claude Code, or handoff --version in a terminal for Codex. The CLI prints 1.0.0.
Claude Code:
/plugin uninstall agent-handoff@agent-handoff
Codex:
/plugins
Disable/uninstall Agent Handoff from the plugin browser, then remove the local marketplace entry if desired:
codex plugin marketplace remove agent-handoffnpm unlink -g @im-ian/agent-handoff # CLI
rm -rf ~/.agent-handoff # local config + hub clone (remote untouched)| Command | Purpose |
|---|---|
/agent-handoff:init |
Register this device, link or create a hub repo. Interactively picks hub setup and device name. |
/agent-handoff:push |
Snapshot the configured agent directory to the hub. Runs secret scan via --dry-run first; AskUserQuestion drives skip/allow/abort on findings. |
/agent-handoff:pull |
Apply another device's snapshot. Shows the device list, previews the diff, asks before overwriting. |
/agent-handoff:diff |
Preview what pull would change, without applying. |
/agent-handoff:status |
Show this device's registration, hub URL, and all known devices with last-push timestamps. |
/agent-handoff:doctor |
Diagnose missing external deps referenced by hooks.json — shows where each missing binary is used and how to install it. |
/agent-handoff:bootstrap |
Install declared deps that are missing on this machine. Always shows the plan and asks before executing. |
/agent-handoff:deps |
Manage the per-device dependencies.json (add <name> --darwin "..." --linux "..." / list / remove). |
Conservative allowlist so unknown files never leak by accident. The default depends on the selected profile.
- Claude default include:
agents/**,commands/**,hooks/**,skills/**,rules/**,mcp-configs/**,memory/**, top-level*.md - Codex default include:
AGENTS.md,config.toml,hooks.json,rules/**,skills/**,commands/**,memories/**, top-level*.md - Hard-deny (always excluded):
projects/**,sessions/**,cache/**,plugins/**,**/agent-handoff/**,commands/handoff-*.md,telemetry/**,backups/**,*.log,*.jsonl,**/.credentials.json,**/.env*,**/*credentials*,**/*secret*,.DS_Store. Theplugins/**/agent-handoff/handoff-*entries keep the agent-handoff plugin itself out of the hub so pulling never restores a stale copy of the very plugin you used to pull — always install it from the marketplace instead. - Custom: edit
scope.include/scope.excludeExtrain~/.agent-handoff/config.json.excludeExtrastacks on the hard-deny list.
Hooks routinely embed absolute paths like /Users/alice/.claude/hooks/format.sh. Sync verbatim → username bob machine → every path breaks.
On push, two literals get rewritten to placeholders. On pull, they resolve back to the local machine's values:
| Token | Replaces |
|---|---|
${HANDOFF_CLAUDE} |
$HOME/.claude for the Claude profile |
${HANDOFF_CODEX} |
$HOME/.codex for the Codex profile |
${HANDOFF_HOME} |
$HOME |
So "command": "node \"/Users/alice/.claude/hooks/x.js\"" becomes "node \"${HANDOFF_CLAUDE}/hooks/x.js\"" in the hub, then on bob's machine resolves to "node \"/Users/bob/.claude/hooks/x.js\"" — automatically correct. Longest pattern wins so path nesting stays right.
${HANDOFF_USER} / ${HANDOFF_HOSTNAME} exist but are off by default — bare usernames false-positive into prose (alice → malice/palace). Opt in via substitutions: [{ "from": "alice", "to": "${HANDOFF_USER}" }] in config when needed.
Every scoped text file (≤ 2 MB) is scanned for: Anthropic/OpenAI/GitHub/Google/AWS/Slack tokens, private key headers, JWTs, generic password= / api_key= literals.
- From
/agent-handoff:pushin Claude Code. A--dry-runpreflight surfaces findings, thenAskUserQuestionoffers skip flagged / upload everything / abort. Public or unknown-visibility hubs get an extra warning. - From the terminal, interactive. Per-file prompt: skip / upload anyway / abort. Non-private hubs require a typed
yesconfirmation. - False positives (Django
SECRET_KEYexamples, test fixtures, password-pattern docs) → add file paths tosecretPolicy.allowin config. Manual edits only — prevents click-fatigue from silently growing the list.
Hooks invoke external CLIs (gh, jq, clawd, rtk, …). After a pull onto a fresh machine, those binaries may not be installed → hooks silently fail with command not found. These Claude Code slash commands address this:
/agent-handoff:deps add gh --darwin "brew install gh" --linux "apt install gh"
/agent-handoff:doctor # confirm gh is declared; see what else is missing
/agent-handoff:bootstrap # install missing declared deps (shows plan, asks first)
/agent-handoff:doctor— read-only scan ofhooks/hooks.json. Shows missing binaries with file:line context and a suggested fix from the manifest./agent-handoff:deps add/list/remove— edits the per-device manifest at<hub>/devices/<name>/dependencies.json.addandremoveauto-commit and push./agent-handoff:bootstrap— installs declared deps that aren't on PATH. Always prints the install plan first, always requires confirmation. Pull never auto-installs anything.
v1 detects from hooks/hooks.json only; scripts/**/*.sh parsing comes in v1.1.
<hub>/
├── devices/<name>/
│ ├── snapshot/
│ │ ├── claude/ # tokenized Claude profile files
│ │ └── codex/ # tokenized Codex profile files
│ ├── version.json # { claude: { pushedAt, host, ... }, codex: { ... } }
│ └── dependencies.json # { claude: { dependencies: {...} }, codex: { ... } }
└── manifest.json # { version: 2, devices: { <name>: { claude: ..., codex: ... } } }
One device holds both claude and codex snapshots side-by-side. Metadata files (version.json, dependencies.json) live at the device root and are keyed by profile internally, mirroring the manifest's shape. pull always reads <source>/snapshot/<your-local-profile>/, so cross-profile pulls are blocked by construction.
One git commit on the hub = one profile push from one device. N devices × M versions emerges naturally from git history. No cross-device merging — /agent-handoff:pull --from X always applies X's snapshot for the current profile atomically.
When handoff init --create-hub creates a new hub, it seeds a README.md covering the layout, the recovery playbook (install plugin via marketplace → install CLI → handoff init → handoff pull), and the day-to-day commands — so the hub repo itself documents how to restore from it.
~/.agent-handoff/config.json — full schema in docs/DESIGN.md. Most users never touch this file; handoff init writes a sensible default.
{
"device": "my-mac",
"profile": "claude",
"hubRemote": "https://github.com/<you>/<hub>.git",
"appDir": "/Users/<you>/.claude",
"scope": { "include": ["agents/**", "..."], "optIn": [], "excludeExtra": [] },
"secretPolicy": { "allow": [] },
"substitutions": []
}AGENT_HANDOFF_HOME env var overrides the config/hub location (default ~/.agent-handoff/) — useful for safe trial runs (AGENT_HANDOFF_HOME=/tmp/trial handoff init …) and per-user isolation in shared environments.
Every Claude Code slash command is a thin wrapper around a matching handoff <subcommand> in your shell. In Codex, use the terminal CLI directly: handoff init --profile codex, handoff push, handoff pull --from <device>, handoff doctor, etc. handoff <cmd> --help shows the full flag listing.
-
fatal: could not read Password for 'https://…@github.com'— set a local credential helper for the hub clone:git -C ~/.agent-handoff/hub config --local credential.helper '!gh auth git-credential'
Multi-account:
gh auth switch --user <login>first. -
Hub visibility
UNKNOWN— non-GitHub host orghmissing/unauthenticated. Treated as potentially public; requires typedyesper file with findings. -
Scope-change churn — widening shows new files as
added; narrowing leaves stale snapshot files (the hub doesn't auto-prune). Re-push from the owning device to rewrite its snapshot.
v1.0.0 — stable. Used in production across multiple devices. npm publish pending.
Roadmap: handoff log --device <name>, handoff pull --at <sha>, auto credential-helper setup on init, scripts/**/*.sh parsing in doctor, SessionStart-hook integration (opt-in "soft handoff"), npm release.
claude-teleport by @seilk covers the same space — "sync agent setup across machines via a private GitHub repo" — and agent-handoff was directly inspired by it. The two projects ended up with different architectural choices worth understanding before picking one:
| claude-teleport | agent-handoff | |
|---|---|---|
| Storage model | Branch-per-device, auto-merged into main |
Directory-per-device (devices/<name>/) on main, no merging |
| Cross-device paths | Synced verbatim | Tokenized — ${HANDOFF_CLAUDE} / ${HANDOFF_HOME} so a hook written on /Users/alice/… runs correctly on /Users/bob/… |
| External dep tracking | — | doctor / bootstrap / deps surface missing CLIs referenced by hooks |
| Public sharing | teleport-share / teleport-from <user> |
Private-hub only (by design) |
| Plugin cache | Synced (plugins + marketplaces included) | Excluded — reinstall from Claude Code /plugin install or Codex /plugins on each machine |
If you want a branch-merged single-source-of-truth with public sharing, pick teleport. If you want per-device isolation, path tokenization, and external-dep tracking, pick this one.
Why a separate project, not a PR? The storage model (directory vs. branch), path tokenization, and dep-tracking surface touch every command — they aren't a patch, they're a different set of tradeoffs in the same problem space. seilk's design is coherent for its use case; agent-handoff explores different ones.
Dotfile managers (chezmoi, yadm, stow) also solve the broader sync problem, but require manual templating for path differences. Both projects above skip that by baking in knowledge of Claude Code's directory shape.
MIT