---
name: a-two-arm-completion-poll-fails-open
description: "A watcher whose exit test is \"reading != the running value\" reports DONE on every meter failure — a dropped ssh, an empty answer, an error string all satisfy it; give the poll three arms and never stop on the third"
metadata:
  type: feedback
---

A completion poll written as `if [ "$r" != "0" ]; then break; fi` stops on EVERY value that is not
the running value — including the values a broken meter returns. Mine (2026-09-07, W1 rig-ports
battery on kitsubito): one `ssh` returned nothing, `$r` was empty, empty != "0", and the loop
printed **BATTERY DONE at iteration 1** while the clippy leg was still mid-flight. I reported a
finished battery to nobody's harm only because I re-read the log tail in the same breath and saw
the contradiction — the exit files said `claim=0 prebuild=0` and nothing else.

Three arms, and the third must NOT stop the poll:

```sh
r=$(ssh box 'if grep -q "battery END" LOG; then echo DONE; else echo RUNNING; fi' 2>/dev/null | tr -d "\r")
case "$r" in
  DONE)    break ;;
  RUNNING) : ;;
  *)       echo "METER BROKEN (got: '$r') — not treating as done" ;;
esac
```

Make the REMOTE side answer with a positive word for each state rather than shipping a count home:
a count crossing a transport can arrive empty, CR-terminated, or as an error string, and all three
compare unequal to the running value. `tr -d "\r"` because a Windows-side shell will otherwise hand
you `RUNNING\r`, which matches no arm and would land in the broken-meter arm forever.

**Why:** this is [[an-absence-is-data-only-if-it-has-a-way-to-appear]] pointed at my own
instrument, and IR-76's three-arm pre-flight ruling (an empty `gh run list` is the same bytes for
CLEAR and for a broken meter) applied to a poll instead of a gate. I had BOTH of those in hand and
wrote the two-arm version anyway, which is the point: the fail-open shape is the one that comes out
of the fingers, because the running state is the one you are thinking about while you type.

**How to apply:** any watcher — a poll, a wait-until, a "has it finished" check — gets three arms
before it is armed. When it does fire, confirm with the artifact the work itself writes (the exit
FILE, per leg) and never with the watcher's own verdict; see
[[read-the-exit-file-not-the-harness-notification]]. The same rule kills the sibling error of
treating a background TASK's completion notification as the completion of the work that task was
watching: my poll tool exited 0 and the harness said "completed", and the battery was still running.
