`status` could only answer running / starting / idle / killed / none,
because every turn ran against `&NoopSink` and the whole stream-json
stream was discarded. "Running" describes a wedged subagent exactly as
well as a busy one, leaving a caller to tell them apart from `ps` output
and CPU-time deltas.
So the daemon now keeps a `name -> last_event_at` clock, bumped by
`LivenessSink` on every line of every stream — stream-json events, plain
stdout chatter and stderr alike — and `status` reports its age on a
running answer: a few seconds means working, an age climbing into the
minutes with no end-of-turn todo means wedged. Nothing is read out of the
content; classifying *what* a subagent is doing is a separate question
and waits on its own driver work. In memory with the rest of this
daemon's state, dropped when the turn ends, no persistence.
The clock is seeded at the spawn rather than at the first line, so a
subagent that wedged before emitting anything still reports a climbing
age rather than no age at all — the case an age is worth most in.
Separately, `continue`'s existence pre-check is gone. It could only
repeat the lookup `Claude::spawn` was about to do, and its message —
"no session named `x` exists" — was false in the common failure: the
session existed, just not under the claude home + cwd `build_store`
resolved from. claude's own `--resume` is the authority and exits
non-zero (`does not match any session title`) rather than quietly
starting a fresh session, so the turn fails on its own. `classify_end`
appends the one fact the CLI's message lacks — the directory searched:
claude error: no session matched the requested id or title (searched
<claude_home> for cwd <cwd>; if the session was started elsewhere,
pass `dir`)
The `dirs` map's durability is untouched; whether to persist it stays an
open operator decision.
Module doc, `docs/tools/subagent.md`, the `continue`/`status` tool
descriptions and the `base:claude-subagents` skill all updated — including
`continue`'s `dir` doc, which said "the daemon remembers it" without
saying that a restart is both when it forgets and when you most want it.
Refs #4330
Refs #4405
docs/tools/forge.md already listed the nine renamed verbs as removed, then
used them ~30 more times in pasteable blocks. Sweeps every occurrence a
reader would type, including three runtime messages that told the user to
run a verb the same binary rejects.
The renamed-verb list itself keeps the old names; it is what documents them.
Refs #4155
Pure `nix fmt` output from the commit before this one — no hand edits.
203 files: 52 md, 42 tsx, 32 js, 32 css, 21 ts, 13 html, 8 json, 3 mjs.
Reproduce with `nix develop -c nix fmt` on the parent commit; the result
should be byte-identical to this tree.
None of the 13 `.prettierignore` entries appears here — verified by
intersecting the changed-file list against the ignore file, with a
control proving the intersection finds a match when one exists.
Hit both today doing a live QA pass on the gateway: plain
nixpkgs#chromium has no color-emoji font (tofu boxes read exactly
like a missing-icon bug), and a page whose content loads async after
the initial paint (SSE, fetch-on-mount) screenshots as a bare
"loading..." placeholder without --virtual-time-budget. Neither is a
real product bug, but both need a documented fix for when an accurate
render actually matters, not just "know to discount it."
Captures a lesson from this session: "no browser in this container"
usually means no browser pre-installed, not unreachable. nixpkgs#chromium
is a `nix shell` away and one headless invocation renders a page +
writes a PNG, no puppeteer/playwright needed for a static screenshot.
Every agent hits "I have no browser" sooner or later when reviewing a
frontend change; this has been rediscovered independently across
several sessions rather than remembered. Belongs in base (every agent
needs it, not role-specific) alongside the sibling skills already
there.