Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Block a script on agent state

Block a script until an agent reaches a state, then act on the result. tma wait is the scripting primitive: it waits on a target pane, prints its final row, and exits with a code you can branch on. This guide covers the common recipes and the exit-code contract in practice.

wait is level-triggered: if the target is already in a requested state, it returns immediately rather than waiting for a fresh transition. For every flag see tma wait; the exit-code table is there too.

Block until an agent finishes

Wait for a specific pane to go idle, then run the next step:

tma wait --pane %5 --until idle && ./deploy.sh

--until takes a comma-separated set, so you can wake on any of several states. Wait for the agent to either finish or get stuck:

tma wait --pane %5 --until idle,blocked

When the state is reached, wait prints the matched row (same columns as tma ls) and exits 0:

$ tma wait --pane %1 --until blocked
%1	claude	blocked	permission	1786900866503	s2:0.0	web-ui	1	

Use --json for a structured result (one schema-1 object, same keys as an ls --json row):

$ tma wait --pane %0 --until working --json
{"schema":1,"pane":"%0","agent":"claude","state":"working","detail":null,"since":1786900866412,"since_ms":1786900866412,"episode_ms":1786900866412,"locator":"s1:0.0","title":"api-server","attention":false,"done":false,"session":"3f1c8a20-5b6d-4e77-9c11-8a2e4d0b6f93","context":31,"context_at_ms":1786900866438,"muted":false,"tokens":62400,"repo":"tmux-agents","branch":"main","worktree":false,"server":"/private/tmp/tmux-501/default","host":"devbox"}

Target an agent by name

--agent waits on the pane running that agent. It pins to the first pane it observes and then behaves as --pane on that id, so a second same-named agent appearing mid-wait never flips the wait. If more than one pane matches at that first observation, it is an error that tells you to target one explicitly, rather than silently picking one:

$ tma wait --agent claude --until idle
tma: --agent "claude" matches 2 panes (%1, %0); target one with --pane

Narrow the match with the selector flags when you have the same agent in several places: --session <name>, --repo, --branch, or --state. They scope the first observation (the one that pins), not the pinned pane afterwards. --any waits on any agent pane in scope and never pins, so it keeps waiting if one vanishes:

tma wait --any --repo tmux-agents --until done --timeout 900

Wait on a fleet

--all is a barrier: it returns only when every agent pane in scope is in a target state, and prints all of their rows. Use it to join a fan-out before a merge step:

tma wait --all --repo tmux-agents --until idle,done --timeout 1800 && ./collect.sh

Membership is pinned at the first observation, so an agent someone launches halfway through does not extend the barrier — the fleet is the one you started over. A member whose pane dies ends the wait at exit 3 rather than quietly shrinking the barrier to the survivors. An --all whose scope matches no pane at all is exit 2 (there is nothing to wait for), never a vacuous success.

--count <n> is the looser form, a quorum: it returns once n panes in scope are in a target state, re-reading the scope every cycle, so panes may come and go under it. Use it to start work as soon as enough agents are free:

tma wait --count 2 --agent claude --until done --timeout 600

Both print one tma ls line per satisfied pane, and both take --json, where they emit the same schema-1 agents document tma ls --json does (rather than the single row object the one-pane targets emit).

Drive a supervisor loop

wait is level-triggered, which is what makes it safe to call at any moment — and what makes a naive loop spin. If you wait for blocked, act, and loop, the second wait returns instantly: the pane is still blocked (or just became idle in a way that satisfies you again) from the episode you already handled. --since fixes that by requiring the state to have BEGUN after a timestamp you carry forward:

#!/bin/sh
# Feed one agent a queue of tasks, one per idle episode.
set -eu
pane=%5
since=0
while read -r task; do
  row=$(tma wait --pane "$pane" --until idle --since "$since" --json --timeout 900) || exit $?
  since=$(printf '%s' "$row" | sed 's/.*"episode_ms":\([0-9]*\).*/\1/')
  tma act queue-next --pane "$pane" --arg "$task" --yes
done < tasks.txt

Each pass blocks until the pane enters an idle episode strictly newer than the one it just serviced, so the loop advances exactly once per episode. The floor is exclusive (episode_ms > --since), which is why feeding back the row’s own episode_ms is correct. Read that key and not since_ms: since_ms is @agent_since, which is write-once per state run, so once the pane has completed a second turn without leaving idle it stays pinned behind the value wait compares, and every lap satisfies instantly. episode_ms is the later of the two, so it always names the episode the loop just handled. --since composes with every target, including --all and --count. The queue-next action it fires is written in Author a custom action.

Gate CI on agent state

In a headless run, launch the agent, then block a build step on its completion with a timeout so a hung agent fails the job instead of hanging it. --timeout follows the timeout(1) convention and exits 124 on expiry:

#!/bin/sh
set -e
# ... launch the agent in a tmux pane %agent ...
if tma wait --pane "%agent" --until idle --timeout 600; then
  echo "agent finished"
else
  code=$?
  [ "$code" = 124 ] && echo "timed out" && exit 1
  [ "$code" = 3 ]   && echo "pane vanished" && exit 1
  [ "$code" = 4 ]   && echo "agent died" && exit 1
  exit "$code"
fi

You can also compose with timeout(1) itself as an external belt when you want a hard wall-clock ceiling regardless of wait’s own logic:

timeout 600 tma wait --pane %5 --until idle

Exit codes in practice

Each code below was produced by a real wait invocation. Branch on them as in the CI recipe above.

$ tma wait --pane %1 --until idle --timeout 2      # never reaches idle
tma: timed out after 2s waiting for idle (exit 124)

$ tma wait --pane %5 --until idle                  # pane killed mid-wait
tma: the waited-on pane %5 vanished before reaching idle (exit 3)

$ tma wait --pane %1 --until bogus                 # bad state token
error: invalid value 'bogus' for '--until <STATES>': unknown --until state "bogus" (expected one of: idle, working, blocked, unknown, done)

For the full code list and its semantics, see the exit-code table.

Waiting before an agent exists

A --pane target that is not yet an agent does not fail fast: wait blocks by design (the agent may launch later), printing a one-time hint to stderr if the pane looks like a typo.

When the agent crashes

Once the wait has seen the pane carrying an agent, a missing agent row means the opposite: the process died and the pane is still sitting there. That ends the wait at exit 4 rather than blocking until --timeout, so a supervisor can restart the agent instead of waiting out a ceiling meant for slow work:

$ tma wait --pane %5 --until idle --timeout 900
tma: the agent on pane %5 exited before reaching idle (exit 4)

Branch on it next to 124: 4 means “restart it”, 124 means “it is still running and taking too long”. --any and --count ignore a departure (they are waiting on whoever else is left); --all ends on a member’s agent death the same way it ends on a member’s pane vanishing, and forwards that member’s own verdict rather than flattening the two. So a barrier exits 4 when a member’s agent died and 3 when a member’s pane went away, which is the distinction the branch above depends on.

Watching everything instead

tma wait blocks for one thing. For a running record of every transition, see Stream state changes.