How it works / Working offline
Working offline
When the tool cannot reach the service, it keeps your write on this machine and sends it later. Until the service accepts it, nothing has happened.
Before you start
The examples use the workspace billing, the handles build-agent and review-agent, and the file baton.md from your first relay. Any baton file will do. This page opens its own pieces of work, BILL-55 and BILL-48.
What the tool keeps
The tool keeps a write when the service cannot be reached or cannot answer. That covers a lost connection, a timeout, a proxy that fails, and a server error. It also covers a stored sign-in that has run out and cannot be renewed. Then the tool also tells you to run handsoff login on this machine.
These writes can wait on your machine:
open,save,offer,retractandclosecontinue, with or without--take-overrenew,end,note,blockandunblockhandle add,handle claim,handle disableandworkspace create
Each kept write is one private file in the tool's state folder, under queue/. It holds the request, but never your sign-in.
What it never keeps
Some commands never wait on your machine:
login,logoutandwhoami- reads, such as
baton,show,list,statusandhistory workspace use,pending,sync,instructionsandchecksadapter,hookandsession end
A refusal is never kept either. If you are not signed in at all, or the tool or the service refuses the write, you see the refusal at once.
A write that waits
First, review-agent opens a piece of work while the service answers:
handsoff open BILL-55 --title "Send the April statements" --as review-agent --baton baton.md
Opened BILL-55 at r1 (work 119)
Leg 211; lease expires 2026-10-05T05:20:34.936689+00:00
To see a write wait, send one save through a proxy address where nothing answers. The tool cannot reach the service, so it keeps the save:
HTTPS_PROXY=http://127.0.0.1:9 handsoff save BILL-55 --as review-agent --baton baton.md
Pending on this machine, not accepted by the record: 1 queued, 0 refused, 0 conflict, 0 unsent; run handsoff pending.
Queued as pending, not accepted: save on BILL-55 (write d1d9f7ee8aba). The record could not be reached: The record could not be reached; the environment proxy http://127.0.0.1:9 may be refusing or unable to reach the network.. Nothing has changed in the record. This machine sends it when the record answers, within 24 hours; run handsoff sync to send it now, or handsoff pending to list it.
The record is the service's copy of the work. Here the reason names the proxy. When your network is down, the reason names what failed instead.
The command exits with code 3. An agent or a script can read the exit code:
| Exit code | Meaning |
|---|---|
| 0 | The service accepted the write, or the command finished. |
| 1 | The service, or the tool, refused it by a rule. |
| 2 | The tool could not reach the service, or could not tell what happened. |
| 3 | The write waits on this machine. The service has not accepted it. |
The pending line
While writes wait on your machine, a command prints this line before its own output:
Pending on this machine, not accepted by the record: 1 queued, 0 refused, 0 conflict, 0 unsent; run handsoff pending.
Every command does this except adapter, hook and session end, which never print it. --help and --version print only the help or the version. Most commands first try to send the waiting writes. If the service accepts them all, nothing waits any more, so you see a Sent from the queue line instead. With --json, the line goes to standard error.
The four counts mean this:
queued: the write waits to be sent.refused: the tool sent it, and the service refused it.conflict: you based a save on a baton that has since changed. The tool merged nothing.unsent: the write is more than 24 hours old, so the tool no longer sends it by itself.
List what waits
handsoff pending lists the writes on this machine, oldest first. It does not try to send them:
handsoff pending
Pending on this machine, not accepted by the record: 1 queued, 0 refused, 0 conflict, 0 unsent; run handsoff pending.
d1d9f7ee8aba 2026-10-05T04:50:35Z (age 0s): save on BILL-55: pending
The first word is the write's id. Each line also shows when you made the write, what it was, and its state.
Send them
To send every waiting write now, with no time limit, run handsoff sync:
handsoff sync
Sent from the queue: save on BILL-55 accepted, as r2.
Queue replay complete; run handsoff pending for anything not accepted.
handsoff pending
No pending writes on this machine.
You do not have to run sync. The next command that reaches the service sends the waiting writes first. It spends at most five seconds on them. Here a note waits:
HTTPS_PROXY=http://127.0.0.1:9 handsoff note BILL-55 --as review-agent --text "The April statements for the first 50 customers are sent."
Pending on this machine, not accepted by the record: 1 queued, 0 refused, 0 conflict, 0 unsent; run handsoff pending.
Queued as pending, not accepted: note on BILL-55 (write 6f72466e9aac). The record could not be reached: The record could not be reached; the environment proxy http://127.0.0.1:9 may be refusing or unable to reach the network.. Nothing has changed in the record. This machine sends it when the record answers, within 24 hours; run handsoff sync to send it now, or handsoff pending to list it.
The next command, here history, sends it before it does anything else:
handsoff history BILL-55
Sent from the queue: note on BILL-55 accepted.
…
The service checks each write it receives from the queue as if it were new. It also checks that the work is still as it was when you made the write. If another carrier has caught the work since, the service refuses your save. It never applies a queued write to a different leg or baton. A leg is one carrier's stretch on the work. See leases and carriers.
The writes for one piece of work go to the service in the order you made them.
After 24 hours, the tool stops sending a waiting write by itself. It shows as unsent until you discard it or send it again by hand.
Discard or resend one
To drop a write you no longer want, give its id to handsoff pending discard. Here a second note waits:
HTTPS_PROXY=http://127.0.0.1:9 handsoff note BILL-55 --as review-agent --text "Statements 51 to 100 are next."
Pending on this machine, not accepted by the record: 1 queued, 0 refused, 0 conflict, 0 unsent; run handsoff pending.
Queued as pending, not accepted: note on BILL-55 (write b272863596f1). The record could not be reached: The record could not be reached; the environment proxy http://127.0.0.1:9 may be refusing or unable to reach the network.. Nothing has changed in the record. This machine sends it when the record answers, within 24 hours; run handsoff sync to send it now, or handsoff pending to list it.
handsoff pending discard b272863596f1
Pending on this machine, not accepted by the record: 1 queued, 0 refused, 0 conflict, 0 unsent; run handsoff pending.
Discarded note on BILL-55 (write b272863596f1).
To send one write again now, use handsoff pending resend. Here a renew waits:
HTTPS_PROXY=http://127.0.0.1:9 handsoff renew BILL-55 --as review-agent
Pending on this machine, not accepted by the record: 1 queued, 0 refused, 0 conflict, 0 unsent; run handsoff pending.
Queued as pending, not accepted: renew on BILL-55 (write f77124434089). The record could not be reached: The record could not be reached; the environment proxy http://127.0.0.1:9 may be refusing or unable to reach the network.. Nothing has changed in the record. This machine sends it when the record answers, within 24 hours; run handsoff sync to send it now, or handsoff pending to list it.
handsoff pending resend f77124434089
Pending on this machine, not accepted by the record: 1 queued, 0 refused, 0 conflict, 0 unsent; run handsoff pending.
Resent renew on BILL-55 accepted.
The pending line above Discarded and Resent comes from before the discard or the resend.
resend reads the work as it is now and sends the write as a new one. A save is based on the current baton. Use it for a write that is old, refused or in conflict.
When a save conflicts, handsoff pending prints your text and the current baton in full. The tool never merges them. Discard yours, or resend it to save your text on top of the current baton.
A catch that waits
handsoff continue can wait too. To see it, build-agent opens BILL-48 and stops on a usage limit with no offer, so the work drops:
handsoff open BILL-48 --title "Send the refund emails" --as build-agent --baton baton.md
Opened BILL-48 at r1 (work 120)
Leg 212; lease expires 2026-10-05T05:20:49.04975+00:00
handsoff end BILL-48 --as build-agent --reason limit
Ended BILL-48: limit
Look at the dropped work while the service still answers, so this machine has seen it:
handsoff status
…
DROPPED BILL-48: Send the refund emails — dropped
Workspace billing (29)
Last carrier build-agent; ended: ended without a hand-over at 2026-10-05T04:50:49.520071+00:00
Last accepted r1 at 2026-10-05T04:50:49.050622+00:00
dropped: ended without a hand-over at 2026-10-05T04:50:49.520071+00:00; held by build-agent; last accepted r1 at 2026-10-05T04:50:49.050622+00:00
Next: catch it
for handles: build-agent, dana, review-agent
…
Now catch it through the proxy that does not answer. A waiting catch has not happened, so the tool prints no baton:
HTTPS_PROXY=http://127.0.0.1:9 handsoff continue BILL-48 --as review-agent
Pending on this machine, not accepted by the record: 1 queued, 0 refused, 0 conflict, 0 unsent; run handsoff pending.
The catch of BILL-48 is pending, not accepted: no catch has happened, this session does not hold BILL-48, and it has no baton to work from until the record accepts the catch.
handsoff sync
Sent from the queue: continue on BILL-48 accepted, as r1. This session now holds the work; run handsoff continue BILL-48 to read the baton.
Queue replay complete; run handsoff pending for anything not accepted.
Do no work on it until the service accepts the catch. Another handle may catch the work first, and then the service refuses yours.
The tool makes the catch against the offer or drop this machine last saw. That is why you ran handsoff status first. If this machine never saw it, the service refuses the catch when it arrives, and you run handsoff continue again.
When the catch goes through, the tool does not show whether the last carrier was interrupted. Run handsoff history and read how the last leg ended:
handsoff history BILL-48
…
Leg 1: build-agent (agent, unknown)
opened at r1; saved r1
runtime unknown and model unknown (as reported); session unknown
ended with reason limit at 2026-10-05T04:50:49.520071+00:00; handed over to review-agent
…
Here the last leg ended with limit, not clean. Read after an interruption before you carry on.
Next, see Claude Code to let an agent carry work for you.