Your agents / Claude Code
On this page

Your agents / Claude Code

Claude Code

The Claude Code adapter connects Claude Code sessions to Handsoff. One command installs it in a project. After that, every session in that project knows about the relay, keeps its lease alive while it works, and is asked to save its baton before it stops.

Before you start

Put the tool on your PATH (Install) and sign in on the machine (Sign in). The agents use your sign-in, so they do not sign in themselves.

The examples use the workspace billing from Your first relay, with its agent handles build-agent and review-agent. They run in a project folder called my-project. BILL-62, in the history below, is work that build-agent opened in a Claude Code session in that project, with the baton.md file from Your first relay.

Install the adapter

Run this from the project folder that Claude Code works in:

handsoff adapter install claude-code --project .
Installed hooks and permissions in …/my-project/.claude/settings.local.json; standing instruction in …/my-project/CLAUDE.md.
Interactive sessions run no project hook until you answer yes to the folder-trust question.

--project names the project folder. Here . means the folder you are in.

Run it again and nothing changes. Both files stay the same, byte for byte.

What it writes

The adapter writes two files in the project and nothing else:

  • CLAUDE.md gets the standing instruction, the paragraph that handsoff instructions prints. The adapter adds it at the end and keeps what is already there. If the file already holds the instruction, the adapter leaves the file alone. Tell your agents shows the text.
  • .claude/settings.local.json gets the hooks and the permission lines below. This is Claude Code's settings file for this project on this machine only. The adapter keeps your other settings in it.

It never changes your own Claude Code settings in your home folder.

The hooks

A hook is a command that Claude Code runs at a set point in a session. The adapter adds seven. Each one runs handsoff hook claude-code <event>:

Claude Code eventCommandWhat it does
SessionStarthandsoff hook claude-code session-startPrints the standing instruction and the first lines of handsoff status. Passes the session's id, runtime and model to the session's later shell commands.
PreToolUsehandsoff hook claude-code tool-useCounts each tool the agent uses since its last saved baton. It prints nothing and never answers a permission question.
PostToolUsehandsoff hook claude-code renewRuns in the background and renews the lease on work the session holds, at most once a minute.
Stophandsoff hook claude-code stopStops the agent from stopping once if it has unsaved work, and asks it to save.
StopFailurehandsoff hook claude-code stop-failureRecords a usage limit or a failed request on the work. The work stays held.
PreCompacthandsoff hook claude-code pre-compactRecords that the session compacted its context. It never blocks the compaction.
SessionEndhandsoff hook claude-code session-endEnds the session's hold on its work.

Each hook has a three-second time limit. A hook never fails the session. When something goes wrong, it writes a line to adapter.log in the tool's state folder and carries on.

Claude Code raises StopFailure only in an interactive session.

When the agent tries to stop

Say the agent opened BILL-62, ran a tool, and then tried to stop without saving. The stop hook answers Claude Code with this:

{"decision":"block","reason":"Save your baton first (`handsoff save BILL-62`), then stop."}

Claude Code gives that reason to the agent instead of stopping. The hook asks only once. If the agent stops again without saving, the hook lets it stop and adds a note to the work: "stopped without saving after 1 tool uses".

When the session ends

When you leave the session in the normal way, at its prompt, the hook ends the agent's leg with the reason clean. A leg is one carrier's stretch on the work. It ends clean only when the agent saved after its last tool use and no failure came after its last turn. Every other end is other, with a note that says why. Here the agent stopped without saving:

handsoff history BILL-62
…
Leg 1: build-agent (agent, unknown)
  opened at r1; saved r1
  runtime claude-code and model claude-sonnet-4-5 (as reported); session 3b9d0f4e-5a61-4c2b-9e7d-1f20c3a4b5d6
  ended with reason other at 2026-10-05T04:21:16.870334+00:00
…
  2026-10-05T04:21:16.685398+00:00 note: {"kind":"info","note":"stopped without saving after 1 tool uses"}
  2026-10-05T04:21:16.87098+00:00 dropped: {"end_reason":"other","reason":"ended without a hand-over"}
  2026-10-05T04:21:16.871516+00:00 ended: {"note":"session ended: prompt_input_exit; 1 tool uses unsaved; stopped without saving after prompt","reason":"other"}

If a program started the session and set HANDSOFF_RUNNER, the hook leaves the end to that program. Other runtimes explains this.

If the agent offered the work before the end, the offer keeps waiting for its receiver. If it did not, the work drops, and any handle in the workspace can catch it. A session that is killed outright runs no hook. Its lease then runs out.

The permission lines

The adapter lets the agent run these commands without a permission question:

continue, save, offer, end, baton, note, renew, block, status, history and instructions.

Each is written as a line such as Bash(handsoff save:*). Claude Code asks you before any other handsoff command, such as open, close or handle add, unless your own settings allow it.

Claude Code's permission check refuses a baton written to a temporary file outside the project, and a pipe into handsoff. So the agent saves with one command and the baton on standard input. The tool prints this form after every catch:

handsoff save BILL-53 --baton /dev/stdin <<'EOF'
## True now
<what is true now>

## Next action
<the next action>
Done when: <the observable result>

## Traps
<what to avoid>

## Evidence
<what you checked and how>

## References
<the references, in the form printed above>
EOF

What the agent sees at session start

When a session starts, the agent sees the standing instruction. Below it come the first lines of handsoff status: the work it holds, the offers that wait for it, and the dropped work it may catch. The hook gives the service half a second to answer. If the service is slower, the agent sees one line in place of the status, as here:

This machine uses Handsoff, a relay for agent work. When you are asked to continue a piece
of work named in the record, run `handsoff continue <name>` first and work from what it
prints: the baton and what it refers to. Save the baton as you work with `handsoff save`.
Before you stop, offer it to the next carrier with `handsoff offer` or end your leg with
`handsoff end`. Never ask for or read an earlier session's transcript.
The status could not be read; handsoff status shows it.

The agent can run handsoff status itself to see the full list.

After a compaction, a resume or a fork, the hook also prints the baton of any work the session holds, and the command that prints all of it. If the agent did work since its last save, the hook also tells it to save its baton now. The whole start text is at most 9,000 bytes.

The hook also hands the session's id, runtime and model to the agent's later shell commands, as HANDSOFF_SESSION, HANDSOFF_RUNTIME and HANDSOFF_MODEL. So the agent's own handsoff commands name the right session. Handsoff records the runtime and model as reported.

Trust the folder

In an interactive session, Claude Code runs no project hook until you answer yes to its folder-trust question. Answer yes when you open the project. A run with no prompt, such as claude -p, asks no trust question.

If you run Claude Code in its sandbox with a list of allowed network hosts, add two hosts to that list: your Handsoff service and your team's sign-in page. The tool needs both.

Name the handle

The agent acts as one handle. Set it in the agent's environment before Claude Code starts:

export HANDSOFF_HANDLE=review-agent

Tell your agents shows the whole flow with a handle.

For one session only

--print writes no files. It prints the same hooks and permission lines as one JSON object:

handsoff adapter install claude-code --project . --print
{"hooks":{"PostToolUse":[{"hooks":[{"async":true,"command":"handsoff hook claude-code renew","timeout":3,"type":"command"}],"matcher":"*"}],…

Pass that object to one session with claude --settings '<json>'. Nothing is written to the project. A program that starts sessions for you uses this form. Other runtimes says more.

Remove it

There is no command to remove the adapter. Remove it by hand:

  1. In .claude/settings.local.json, delete each hook whose command starts with handsoff hook claude-code. Under permissions, delete the allow lines that start with Bash(handsoff . If the adapter made the file and you added nothing to it, delete the file.
  2. In CLAUDE.md, delete the paragraph that starts "This machine uses Handsoff".

Your sign-in and the work in the record stay as they are.