Mine rather than the mechanism's, and recorded because the mechanism invites it: having acted on a fire, I armed a fresh watcher while the previous one was still alive. Two watchers on one branch produce two notifications for one event, and the second is indistinguishable from a real second push. It compounds the self-trigger fault above — the agent pushes, both fire, neither has anything to report. The discipline is dull: stop the watcher before you push, re-arm after. A pgrep before arming costs nothing and catches it. For Command HQ it is a hazard rather than tidiness. A commander armed twice is two processes doing the same work behind one card. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.8 KiB
Background jobs: what broke while actually running one
Written 2026-08-18 by the mobile side, after arming, firing, draining and re-arming a real watcher
(poll git ls-remote on this repo, react to each push) through the Background Job dialog.
Command HQ is a home for these, so anything wrong with the mechanism shows up there first and worst: a commander is a watcher that is supposed to run for months.
Everything below is from running one, not from reading the code.
1. "Keep going" and "tell me" are mutually exclusive, and the dialog offers them as if they were not
The dialog's two options are Stop when it fires and Keep going — act each time and carry on. The preamble expands the second into: "Do NOT exit when it fires... the stream is the notification, not the exit."
That is true for a human watching a log. It is false for the agent, and the agent is who was asked to react. A background process reaches the model only when it exits — that is the harness's notification. A job that loops forever can do shell work and nothing else. It cannot read a diff, cannot judge whether the push matters, cannot write code.
So "keep going" silently means "the agent will never see this again". For a watcher whose whole point is "when it fires, go and do something", that is the wrong default and it fails invisibly: the job is alive, the output is accumulating, and nobody is reading it.
What the owner actually wanted, worked out in conversation and now the shape we use:
exit on change → agent acts → drain (check again immediately) → act again if more arrived → re-arm the watcher when nothing is left
Asked for: a third option in the dialog, something like "Exit and hand back to the agent, then re-arm", and it should be the default for anything the agent must react to. The existing two are then honest: stop-on-fire is one-shot, keep-going is for a job whose output is the product.
2. Drain before re-arming, or pushes are lost
Not a nicety. Between the job exiting and the agent re-arming, more can land. Re-arming from a baseline captured before the work began re-fires immediately; re-arming from "now" silently swallows whatever arrived in the gap.
The rule that works: after acting, ask again before re-arming. If something new is there, act again. Only when the source is quiet does the watcher go back on. The owner specified this himself; it should be in the preamble rather than rediscovered per job.
3. A watcher on a shared branch fires on the agent's own writes
Observed within an hour. The watcher polled master on this repo; the agent then pushed a COMMS note to
master; the watcher fired; there were no new commits to read.
Author filtering does not help here — the platform side and the mobile side are both Claude, so the commits are indistinguishable by author.
What works: re-baseline from local HEAD after any push of your own, which is what a
git pull + git rev-parse HEAD naturally gives you. Worth stating in the preamble as a rule for any
watcher whose subject the agent also writes to, because the failure looks exactly like a real event.
3b. Re-arming without stopping the old one leaves two watchers
Mine, not the mechanism's, and worth recording because the mechanism invites it. Having acted on a fire, I armed a fresh watcher while the previous one was still alive — the old one had fired but its process had not been reaped yet. Two watchers on the same branch then produce two notifications for one event, and the second looks exactly like a real second push.
It compounds §3: the agent pushes, both watchers fire, and neither has anything to report.
The discipline that works is dull and reliable: stop the watcher before you push, re-arm after. Not
"re-arm when convenient". A pgrep for the job before arming a new one costs nothing and catches it.
For Command HQ this is a real hazard rather than a tidiness point — a commander that has been accidentally armed twice is two processes doing the same work, and the grid would show one card.
4. timeout is not available, and the preamble insists on it
Rule 6 says "timeout every network call, or a hung one leaves it alive and blind." Correct in
substance. But this machine is a Mac, and macOS ships neither timeout nor gtimeout — so the rule
as written cannot be followed, and following it literally produces a job that dies instantly on
command not found.
What was used instead, and what the preamble should suggest for a git probe:
GIT_SSH_COMMAND='ssh -o ConnectTimeout=15 -o ServerAliveInterval=5 -o ServerAliveCountMax=3'
The general point: the rule should name the outcome (no unbounded network call) and give a portable example, rather than name a binary that is missing on the machine most of this runs on.
5. A job the owner stops is reported as a failure
Stopping a watcher by hand surfaced as "failed with exit code 144". Rule 7 already anticipates the confusion from the agent's side — "an exit is at least as likely to be me as it is to be a failure" — but the harness still presents a deliberate stop as a fault.
For Command HQ this matters more than it does in /chat: a grid of cards showing "failed" for every
watcher the owner turned off would be actively misleading. Worth distinguishing stopped-by-request from
died, at whatever layer knows the difference.
6. The working directory offered was the wrong one
The dialog filled in /Users/pastilhas — the conversation's cwd — for a job that had to run inside a
git checkout. It was correct behaviour, and still the wrong answer, because the directory a watcher
needs is a property of what it watches, not of where the conversation happens to be.
Command HQ makes this moot for commanders, since they all share one directory by design. Noting it for
the dialog as used from /chat.
What worked, and should not be traded away
"Ask before arming" earned its place immediately. One question — exit or keep going — changed the whole design of the job. The instruction that a question is the only thing that may come before starting is good discipline and produced exactly the right conversation.
The output rules are right. One line, no preface, no reading the output back to check it started.
One caveat on those rules: they forbid saying anything except a question or that one line, which left nowhere to report a mechanical impossibility — that "keep going" cannot notify the agent. That was worth more than the terseness cost. Suggest allowing, explicitly, a statement of the form "what you asked for cannot work as specified, here is why" alongside the question.
The preamble text itself is the feature. It is what makes a job tracked rather than orphaned, and copying it verbatim into the mobile client — diffed to confirm — was the right call over paraphrasing.