Repository navigation
Description
Problem
scripts/dsh-superagent-run.sh (new in #1084) promises at :18-19: "Exit 0 once the run is detached and DIR/waiting.json is written". It does that unconditionally — the final python heredoc prints the report and exits 0 whatever the child did, and die fires only when tmux/setsid refuses the session (:71, :76). A child that refuses to start therefore reads as a successful handoff: the report says "status": "waiting" with found: false, and the exit code says 0. The only trace of the real reason is DIR/worker.log, which nothing in the report points at as a failure.
This is not hypothetical: it is how #1084's own documented-command defect stayed invisible. The page's example omitted --usd-per-call, the CLI exited 2 in the child before creating anything, and the wrapper reported a handoff.
Evidence
Measured at c69767d7 (the tip of #1084), with the page's command minus its --usd-per-call — the exact case the page now warns about:
$ DSH_RUN_HANDOFF_WAIT=3 bash scripts/dsh-superagent-run.sh --store "$S" -- \
agentdescent --store "$S" evolve spec.json --detach --budget 5
waiting (no run yet) round 0 …/ln-2
report …/ln-2/waiting.json
WRAPPER_EXIT=0
$ cat "$S/waiting.json"
{"command": "... evolve spec.json --detach --budget 5", "driver": "tmux",
"found": false, "run_id": "", "round": 0, "state": "unknown",
"status": "waiting", ...}
$ cat "$S/worker.log"
error: budget_usd needs usd_per_call: the engine counts calls, and a tool-using agent's price per call is not known in advance
The same shape reproduces with any child that exits before writing status.json (agentdescent wrong-flag, a typo in the spec path, a missing interpreter). The successful path is unaffected: with --usd-per-call 0.01 the wrapper returns in ~1 s with found: true, a real run_id, and state: running.
Why it needs a decision, not a one-line patch
found: false alone cannot tell "the child is alive but has not written its status yet" from "the child is gone". The wait window exists because the first case is legitimate (:79-81, and the page's "a handoff into a used store reports found: false until its own run writes one"). So the fix has to decide liveness at the end of the window:
- the driver knows how to answer it —
tmux has-session -t =SESSIONis false once the session's command ends (the existing smoke test already relies on exactly that,tests/test_dsh_superagent_run.py), andsetsidgives a pid tokill(pid, 0); - when the child is gone and no fresh
status.jsonappeared, the handoff failed and the wrapper should say so: nonzero exit, astatethat is notwaiting, and the report namingworker.logas the place the child's own reason sits.
Acceptance
- A child that exits 2 before creating anything → the wrapper exits nonzero;
waiting.jsonhasfound: false, astatethat is neitherwaitingnorrunning, and a field namingDIR/worker.log. - A child that starts and takes the full window to write its
status.json→ unchanged: exit 0,found: true, realrun_id(the existingfake_run.pysmoke already covers the fast case; this row is the slow one). - A handoff into a reused store that writes nothing →
found: falsewith exit 0 (not a failure): nothing was refused, the child just has not written yet. Keep the current row green. - The five rows in
tests/test_dsh_superagent_run.pystay green, including thetmuxno-orphan row.
Non-goals
- Watching the run after the handoff, retrying it, or adding a daemon/scheduler.
- Changing the
DSH_RUN_HANDOFF_WAITdefault. - The stale-tmux-server environment question (a session started by an older
PATHruns a differentagentdescentthan the caller's); that is a host-setup matter, recorded separately.
Activity