You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A run no longer starts on top of one that is still going. ask.sh clears every answer file before launching, and a claude/codex from an earlier run on the same --out-prefix then finished into a deleted inode: its runner found an empty path and marked it NO OUTPUT with exit 0 while the transcript held a full review. Measured 2026-09-07: four branches reviewed in one session, all on $RUN/review, and three sets of GLM and OpenRouter answers went that way — that is what "OpenRouter and GLM don't work" was. ask.sh now takes a lock (bash's own noclobber open — not mkdir: the uutils coreutils that Ubuntu 25.10+ ships answer 0 to both of two racing mkdirs, measured 17 of 30 races on tmpfs), reads the previous run's roster, and refuses the prefix while any of its markers belongs to a live process, saying what to do instead; a marker whose pid is gone is a leftover, not a block. Markers are written whole and renamed into place, so a reader never sees a half-written one as "nobody here".
<answer>.running says who and since when: <pid of the backend's runner> <start epoch> <timeout>, so an ls beside an empty answer shows three minutes in from thirty. The pid is the backend's own subshell, not ask.sh's: it outlives a SIGKILLed ask.sh and keeps writing, and liveness has to mean that process. It used to be an empty file (#27).
<prefix>.run is the roster of a run: one line per participant, written before anything launches, then <backend> <seconds> as each one ends — so a reader that arrives mid-launch sees the whole run, and the durations say which backend was the slow one. scripts/wait.sh --prefix <prefix> [--max N] reads it, blocks until every backend has ended, and prints one line per backend — codex 5m12s ok, glm 23m04s ok, openrouter 40m00s FAILED: <the .dead text>, or still running (timeout 2400s) when --max ran out first (exit 1: call again; a Bash tool call is capped at ten minutes). A marker nobody owns beside an answer means the runner was killed and the answer is partial, and it says so. Status is read from the roster and markers only, never from answer text and never from a glob (review-* would also match a review-2 beside it). The review and check-if-done skills wait through it, say outright that an empty answer beside a live .running is a reviewer still writing, and say that ask.sh runs as a background task — GLM answered after 23 minutes with the one finding nobody else had, and had been written off at nine (#27).
A runner that ends with nothing written and no marker is marked NO OUTPUT in its own subshell, the moment it ends; the parent used to do it only after every backend was done, so a reader in between saw a finished backend with no answer and no reason.
snapshot.sh --paths "<paths>": every path named must be in the final copy, or the snapshot fails (exit 2, no path on stdout, so the skill's "snapshot failed — not reviewing" guard fires) and says why — docs/item-map is not in the copy: ignored via .git/info/exclude:40, from git check-ignore -v (the source and line only; the pattern is text from the reviewed repo). The copy takes git ls-files --exclude-standard, which honours .git/info/exclude, so a docs/ line there silently dropped the folder under review and Codex, OpenRouter and three sub-agents all agreed there was nothing to review. The check runs after the config purge, against the tree the reviewers get; a harness rule file (CLAUDE.md, AGENTS.md, .mcp.json, …) is stripped from the copy on purpose and is noted, not failed — its change is in review.diff; a path the diff deletes or renames is not missing either; a glob is not checked and says so; a path that does not exist at all fails the same way. The review skill passes --paths whenever the target is paths (#29).
The skill headers work in a git worktree session. Claude Code gates every shell command there statically and refuses sh -c, bash <file>, ${VAR:-default}, for loops and $PWD arguments (measured 2026-09-07 with claude -w); the probe header was a sh -c loop, so /multi:code-review died on line one with Shell substitution failed, reading as a broken plugin. It is now a || chain of plain paths — "$CLAUDE_PLUGIN_ROOT/scripts/probe.sh" || "$HOME/.claude/skills/multi/scripts/probe.sh" || ./.claude/skills/multi/scripts/probe.sh — which the gate lets through; probe.sh prints scripts-dir: itself. Every skill names the failure and the one-command workaround for the case the gate changes again (#26).
setup.sh status times the one-token check of the model that answered and prints it (OK — will use x (1s to answer one token right now)); five seconds or more adds SLOW — expect a review here to take tens of minutes; a bad choice for the default profile. One token in seconds is eighty review turns in minutes; nothing measured that before a run. It is one sample at one moment, and says so (#28).
The isn't described by this version's model catalog … auto-compact keeps this session within 200k tokens paragraph is now named in the TIMEOUT / NO OUTPUT marker for what it is: Claude Code's context-window notice for a model name it does not know, printed on every run on a non-Anthropic endpoint, not the cause. It was the only thing in the stderr log of a run that died silently, and read as the reason (#28).
config.example.toml: OpenRouter's free/flash pools are out of the everyday profile (normal = ["codex", "glm"]) — measured 2026-09-06, 25–40 minutes for one review on qwen3.8-flash via OpenRouter while the same model on a direct key took 5. They belong in a profile picked on purpose, and the free profile pins openrouter:z-ai/glm-5.2:free rather than the bare backend, whose chain tries paid models first (#28). Not done from #28: a timeout derived from a measured speed — the pools' speed changes by the hour, and a measured 5-minute pool killing a 20-minute review would hide answers the current fixed budget keeps.
Known limit, left as is: liveness is kill -0 on the pid in the marker. A pid recycled by an unrelated long-lived process after a SIGKILL would hold the prefix until that process exits; the fix is --out-prefix something else. No portable way to tell a recycled pid from the real one was worth its size.