Your agent finished. How would you know?
I built pwt jobs wait so an agent could block on a background job instead
of asking “is it done yet” every thirty seconds. Then I counted how often my
agents actually used it.
Across 134 Claude Code session logs, 23,770 shell commands:
sleep <n> 251
until ... done 75
while kill -0 <pid> 27
tail ... log 122
pgrep 109
pwt jobs wait / server wait 5
Five. The primitive existed, was documented, and my own agents wrote their own wait loop a hundred times instead. This post is about the problem, the four ways the field solves it, and the honest conclusion about mine.
What an agent writes when it has to wait
These are real commands out of my logs, with paths generalized:
until [ -f "$WORK/gate.DONE" ]; do sleep 60; done; cat "$WORK/gate.DONE"
while kill -0 91829 2>/dev/null; do sleep 30; done; echo "finished"
until ! pgrep -f 'parallel_rspec' >/dev/null; do sleep 60; done
Every one of those is a wait primitive, reinvented from scratch, in a one-liner, by a model that had to pick the interval by guessing. Pick 60 seconds for a job that takes 12 and you waste 48 seconds of wall clock. Pick 5 for a job that takes 20 minutes and you have burned 240 tool calls and the tokens for each of their outputs.
The failure mode nobody notices is the third one: pgrep -f parallel_rspec
matches the agent’s own until loop in some shells, so the loop can exit
immediately or never. Hand-rolled waits are quietly wrong in ways nobody
reviews, because they live inside a command nobody reads twice.
This is a recognized problem, not my invention. Anthropic shipped a Monitor tool in April 2026 specifically to replace polling with interrupt-driven waiting.
Four ways the field handles background work
Put it in a tmux pane. This is the popular answer. workmux (2,179 stars) and claude-squad (8,304 stars) both give each worktree its own pane or window, and you watch the fleet by attaching.
It is genuinely good for a human. It is useless for an agent: a pane has no exit code, no addressable id, and no way to answer “did it succeed” except by scraping text off a screen. It also binds the tool to Unix, since there is no native tmux on Windows.
Refuse to own the process. The category leader, worktrunk (6,417 stars), states it in its FAQ, verbatim: “No long-running background processes or daemons”. That is a defensible design. It keeps the tool small, and it means the dev server is your problem.
Watch the output stream. Claude Code’s Monitor tool attaches to a running process and fires when a pattern appears, like “Server running on port 3000”. This is the right shape, and it is first-party. Its scope is the session: it watches a process the current session started.
Keep a registry. The process gets an id, a log file, a recorded pid and a status, so anything can ask about it later, including a different session tomorrow.
What the registry buys, concretely
Start something in a worktree without holding the terminal:
pwt <worktree> <command> --bg
# x-slowtask-1786647159
Then block on it, from anywhere, with one tool call:
$ pwt jobs wait x-slowtask-1786647159
x-slowtask-1786647159 stopped
# blocked 3.6s on a 4s job, exit 0
That is the whole point. One tool call, no interval to guess, an exit code to branch on. Bound it when you do not trust the job:
$ pwt jobs wait x-slowtask-1786647173 --timeout 1
Timeout: job still running after 1s: x-slowtask-1786647173
Check it with: pwt jobs logs x-slowtask-1786647173
$ echo $?
5
Exit 5 means timeout, distinct from a job that ran and failed. For a server, readiness is not process exit, so wait on the log line instead:
pwt server wait <worktree> --log-contains "Listening on"
And the state is queryable rather than scraped:
$ pwt jobs list --porcelain
[{"id":"x-slowtask-1786647173","command":"slowtask","worktree":"x",
"project":"main","pid":17971,"log":".../x-slowtask-1786647173.log",
"started_at":"2026-08-13T18:52:54Z","status":"stopped"}]
The difference that matters against a tmux pane is not ergonomics, it is addressability. The job outlives the session that started it, it has a name you can pass around, and the answer to “did it work” is an integer.
When to use which
Honestly, most of these coexist. What decides is who is asking and how long the thing lives.
| Situation | Reach for |
|---|---|
| You want to watch several agents work, with your eyes | tmux based tooling, and it is better at this than a registry |
| One process, this session, wake me when it prints something | the Monitor tool, interrupt driven and first party |
| An agent must branch on the result | a blocking wait that returns an exit code |
| The job outlives the session, or another session asks about it | a registry with ids and logs |
| You do not want the tool touching processes at all | worktrunk’s position is a real one |
The registry is not better than a pane. It answers a different question, and the question it answers is the one an agent asks.
Questions this always raises
Does the job die when I close the terminal? No. Launching goes through a
double fork with setsid, and the daemon closes every inherited descriptor
above stderr. That last part is not pedantry:
one leaked descriptor once hung a CI job for six hours.
How do I know it is ready, not just running? Those are different
questions. Process exit is jobs wait, service readiness is
server wait --log-contains. A server that is running and not yet accepting
connections is the classic false green.
Can two worktrees run the same service at once? That is a port question, not a job question, and it is answered by deriving the port per worktree rather than copying it.
What happens to jobs I forget? They keep their record. pwt jobs list
shows them, pwt jobs stop --all ends them, pwt jobs clean drops stale
entries.
The part I got wrong
Five uses in 23,770 commands is not a story about competitors lacking a feature. It is a story about a feature nobody found, including the agents running inside the repo that ships it.
The tool was not the problem: the primitive works, and every measurement in
this post came from running it. Discovery was the problem. An agent reaches
for until ... sleep because that is what it already knows, and it will keep
doing that until the tool’s own documentation tells it, at the moment it is
about to wait, that a wait already exists.
That is why the agent-facing guide (pwt skill) leads with the wait
primitives now, and why the help text for --bg prints the wait command next
to the logs command. Whether that moves the number is measurable, and I will
report it either way.
Shipping a primitive for agents? Grep your own session logs for the workaround it was meant to replace. If the workaround is still there, the primitive is not shipped, it is just written.
pwt jobs, --bg and the wait primitives are part of
pwt, a Git worktree manager for parallel
development: brew install jonasporto/pwt/pwt.
You do not have to memorize any of this.
pwt skillprints the agent-facing guide to pwt; tell your agent the outcome you want and point it there, and it works out the mechanism and verifies it in a throwaway worktree.