Skip to content

im-ian/agent-handoff

Repository files navigation

English | 한국어

agent-handoff

Hand off your agent setup between devices — sync ~/.claude/ or ~/.codex/ across machines.


Why

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.


Quick start

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 link

Then 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 push

Optionally install the local Codex plugin so new Codex sessions can load the Agent Handoff skill:

codex plugin marketplace add /path/to/agent-handoff
codex

Then 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.


Installation

1. Claude Code plugin

/plugin marketplace add im-ian/agent-handoff
/plugin install agent-handoff@agent-handoff
/reload-plugins

Updates ride through /plugin update.

Codex plugin

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
codex

Inside 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-mac

2. handoff CLI backend

The 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 link

Verify with /agent-handoff:status inside Claude Code, or handoff --version in a terminal for Codex. The CLI prints 1.0.0.

Uninstall

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-handoff
npm unlink -g @im-ian/agent-handoff   # CLI
rm -rf ~/.agent-handoff               # local config + hub clone (remote untouched)

Claude Code slash commands

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).

What gets synced

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. The plugins/** / 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.excludeExtra in ~/.agent-handoff/config.json. excludeExtra stacks on the hard-deny list.

Tokenization

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 (alicemalice/palace). Opt in via substitutions: [{ "from": "alice", "to": "${HANDOFF_USER}" }] in config when needed.


Secret scanner

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:push in Claude Code. A --dry-run preflight surfaces findings, then AskUserQuestion offers 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 yes confirmation.
  • False positives (Django SECRET_KEY examples, test fixtures, password-pattern docs) → add file paths to secretPolicy.allow in config. Manual edits only — prevents click-fatigue from silently growing the list.

Dependency management

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 of hooks/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. add and remove auto-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 layout

<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 inithandoff pull), and the day-to-day commands — so the hub repo itself documents how to restore from it.


Configuration

~/.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.


Terminal usage (optional)

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.


Troubleshooting

  • 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 or gh missing/unauthenticated. Treated as potentially public; requires typed yes per 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.


Status

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.


Related work

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.


License

MIT

About

Hand off your Claude Code setup between devices — across machines.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages