handover-to-server
Where the scripts live.
${SKILLS}below means theskills/directory this SKILL.md sits in:${CLAUDE_PLUGIN_ROOT}/skillswhen installed as theserver-handoverplugin, or~/.claude/skillswhen installed withinstall.sh. Resolve it once, then run the scripts from there.
Pushes the current project to the home server and starts Claude Code there in a
detached tmux session, already working on a prompt if you give one. One script:
rsync (repo + configured extra paths + guarded files) → remote setup command →
pre-accept folder trust → tmux + claude "<prompt>". It is a push (this
machine → server).
Driver: ${SKILLS}/handover-to-server/handover.sh. Run it from inside
the project; do not re-derive it.
Run
${SKILLS}/handover-to-server/handover.sh "continue the UX work — run pytest first"
No argument = sync and launch an idle session. It prints the attach command for the user to run in their own terminal (interactive TTY — hand it over, do not run it from the agent):
ssh -t user@host 'tmux attach -t <session>'
HANDOVER_ATTACH=1 attaches at the end (only when a human runs it directly).
First-time setup (the script tells you)
- exit code 2 → server not configured. Run
/setup-server(server step). - exit code 3 → no
.handover.envfound from the current dir upward. Run/setup-server(project step). Ask the user what belongs to the project: the repo, any data dirs, any irreplaceable DB, a setup command. Then re-run.
Config discovery: walks up from $PWD to the first .handover.env; that dir
is the launch dir. Override with HANDOVER_PROJECT_CONFIG=/path/.handover.env.
What it syncs (and where)
Local $HOME-rooted paths mirror onto the server's $HOME
(/Users/me/repos/x → /home/me/repos/x):
- the repo with its full
.git(local-only branches/tags travel) HANDOVER_SYNC_PATHS— extra dirs/files, this machine canonicalHANDOVER_GUARDED_FILES— backed up on the server first; skipped with a warning if the server copy is newer (HANDOVER_FORCE=1to override)- global
~/.claude/CLAUDE.md(opt-outHANDOVER_SYNC_GLOBAL_CLAUDE_MD=0) and this project's Claude memory dir
Verify a launched session (agent, headless)
ssh -o BatchMode=yes user@host 'tmux capture-pane -t <session> -p | tail -15'
Knobs
HANDOVER_FORCE=1— push guarded files even if the server copy is newerHANDOVER_SKIP_SETUP=1— skipHANDOVER_REMOTE_SETUPHANDOVER_ATTACH=1—ssh -tattach at the endHANDOVER_PROJECT_CONFIG— explicit.handover.envpath
Gotchas (all hit and handled)
- Guarded files are irreplaceable and the server is also worked on. Naive push would overwrite server-side work; hence the newer-wins guard + backup + sha256 verify.
- Trust dialog. A profile that has never seen the launch folder stalls on
"Is this a project you trust?" and never runs the prompt. The driver sets
hasTrustDialogAcceptedfor the launch dir in the remote.claude.json(HANDOVER_CLAUDE_CONFIG_DIRpicks the profile). - Prompt injection through ssh→tmux→shell. Quoting is a minefield, so the
prompt is written to
~/.handover/<session>.prompton the server and the launcher reads it withclaude "$(cat …)". Empty file → plainclaude. - tmux name collisions. Auto-increments (
x→x-2) instead of clobbering and prints the name actually used. - Non-interactive rc. Ubuntu's
.bashrcearly-returns for non-interactive shells, so nothing is sourced; the launcher exports what it needs itself and the remote setup runs underbash -lc. - Non-ASCII paths (
øetc.) are preserved; the Claude project slug isre.sub(r'[^a-zA-Z0-9]','-', path), computed per side.
Troubleshooting
✗ cannot reach <server> over key-based SSH—BatchMode=yes, no password prompts.ssh-copy-id user@hostfirst.- Remote Claude sits at a trust prompt anyway — the remote profile's config dir
differs; set
HANDOVER_CLAUDE_CONFIG_DIRin~/.claude/handover/server.env. - Remote setup failed — open the session and finish it by hand; the driver only warns.