Files
stack/agents/researcher/work/queue-80/r1/BUILD.md
T
jason.woltjeandClaude Opus 5.5 11bf4cb00a researcher: row 80 BUILD.md, Console follow-up and fixes (#1574)
Filbert's R1 (comment 28188): the brief requires the packet to name what
the Console would need to show the stranded list. Added as a Console
follow-up section by the subagent, read-only, with unchecked items named.
Also N1 (M12 note) and N2 (round 1 title). Candidate unchanged, manifest
52c41526. Sage corrected one count: M12 failed 6 of 11 tests, not 13 (the
13 was the count of x-marker lines).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-13 10:26:12 -05:00

11 KiB
Raw Blame History

Row 80 (#1574) build, round 1

Status: Phase B ran three times on this packet. Run 1 failed in bus (119/120). Run 2 passed bus (120/120) and business (60/60) after the fix, then stopped at cli (exit 1) because the worktree had no node_modules. Run 3 was the full gate with a node_modules symlink in place: all 14 node suites and the nine scripts/test-*.sh exited 0, and mutants.py (15 mutants) exited 0 with all 15 killed. Not committed, not pushed, queue row not moved.

Identity

  • Row 80: bus, "stranded delivery" display. Brief: docs/plans/2026-10-11_bus-stranded-delivery.md (not edited).
  • Owner: researcher (decisions 122, 129). Reviewer: Filbert (approved, comment 28188). Dispatch: Sage (lead decision 129).
  • BASE: 57a2ddc5978b05b780dc0e69fb4e6c2a60ab5877 (see base.txt). Phase A used 2b2a9e14eec4ff81cdada5dc42bbf1b8063b29b4; the rebase to 57a2ddc59 changed none of the three paths.
  • Worktree: .claude/worktrees/agent-abe72e00ca2bcdf7e, detached at BASE, changes uncommitted. No commit, no push, no queue move.
  • Candidate manifest: candidate-manifest.sha256, checked with sha256sum -c from the worktree root: all three paths OK.
Path sha256
packages/bus/README.md 02fae2939accd20f1032b0f5d4a3f4f3ca3b5da29606a6f4cee2b15a1da985b0
packages/bus/src/broker.mjs f685af1c5c496f0f1907995919017b9b098f7d84d9aa1047d126f3f4cc4991ef
packages/bus/tests/agents.test.mjs 076137bca90b6594cc0888fb0daa2c57903fff5f1035122c00dd5540de65f6eb

build.patch is git diff against BASE, limited to these three paths.

Changes

  • Fix (Sage's direction, in broker.mjs, not the test): the stranded query at L1047 to L1052 ends with ).map((m) => ({ id: m.id, from: m.from, run: m.run, at: m.at }));. The .map is on L1052. Cause: node:sqlite returns rows with a null prototype, so assert.deepStrictEqual against plain objects fails although the values match. The test expectation is unchanged.
  • node --test tests/agents.test.mjs passes 11/11 after the fix.
  • Mutant M15 added (.map removed, expected killer agents.test.mjs:174 prototype check). The table has 15 entries.
  • phase-b.sh stops on any nonzero step exit. A stop writes STOP: <step> exited <rc>, skips the rest, runs the cwd scan and ends PHASE-B-STOPPED. A full pass ends PHASE-B-DONE.

Phase B runs

Run 1

  • bus exit 1, 119/120. agents.test.mjs:164 failed at L174 (null prototype, as above).
  • Stopped on TERM (exit 143) on purpose, before the fix was applied. A /proc check found no leftover test process.

Run 2

  • Run after the .map fix.
  • bus exit 0, 120/120. business exit 0, 60/60.
  • cli exit 1: 131 total, 121 pass, 8 fail, 2 cancelled. The stop rule fired. The remaining 11 suites, the nine scripts and the mutants did not run.
  • Cause: no node_modules in the worktree. The seat sandbox (packages/seat/src/sandbox.mjs:95, binds) calls realpathSync on <worktree>/node_modules, which fails with ENOENT. The failing trace runs through packages/seat/src/session.mjs:122 and packages/cli/src/launcher.mjs:237. Two launcher tests then time out at 30 s (tests/launcher.test.mjs:471). The rest of the failures are the same ENOENT.
  • Scope: the candidate touches only packages/bus, and no package imports another workspace package (checked in Phase A). The cli failure is a property of the worktree layout, not of row 80. I did not run cli on 57a2ddc59 in a clean tree to prove this on BASE.
  • The cwd scan reported three processes with a cwd in the worktree at the stop (pids 1353827, 1359345, 1362919). A re-check a few seconds later found none.

Run 3

  • node_modules symlinked from the canonical root into the worktree. git status --porcelain showed the three candidate paths, ?? node_modules (expected: .gitignore line 2 is node_modules/, which matches directories only, so a symlink shows as untracked), and ?? agents/researcher/work/queue-80/. Nothing else. Sage's ruling on this status is recorded in the coordinator message, not decided here.
  • Full phase B from the top. Summary in out/phase-b-summary.txt, end marker PHASE-B-DONE.
  • Node suites, all exit 0: bus (120/120), business (60/60), cli (passes with the link; count in out/), control-board, conversation, discord, harness, ledger, mosaic, queue, runs, seat, tasks, webui.
  • Scripts, all exit 0: test-auth, test-conductor, test-config, test-discord, test-extension-package, test-foundation, test-queue, test-release, test-task.
  • The cli pass confirms the run 2 cli failure was the missing node_modules.
  • Two attempts did not run phase B. A symlink attempt in the earlier round was stopped by the untracked-link rule. A later chained command was refused by the worktree guard. Neither changed the candidate.

Cleanup after run 3

  • rm <worktree>/node_modules (plain rm, the link only).
  • git status --porcelain shows only the three candidate paths and the packet.
  • build.patch covers the three paths only.
  • sha256sum -c verifies all three candidate hashes.
  • A scan from / finds zero processes with a cwd in the worktree.

Mutants (15), phase B run 3

Each mutant is one exact-once replacement in broker.mjs. The "Observed" column lists the agents.test.mjs line numbers that appear in the stack frames of out/M<nn>.log. The expected killer line appears in every log.

ID Site Expected killer Observed assertion frames
M01 broker.mjs:47 (STRANDED cap) :203 length 50 203
M02 broker.mjs:1048 (sort order) :204 oldest-first ids 204
M03 broker.mjs:1048 (cap) :203 length 50 203
M04 broker.mjs:1048 (current holder) :189 current holder keeps unread 189
M05 broker.mjs:1048 (released run) :174 released-run entry 174, 189, 203, 221
M06 broker.mjs:1048 (read filter) :192 read message not listed 192
M07 broker.mjs:1048 (business scope) :230 per business 230
M08 broker.mjs:1048 (role scope) :221 per role 221
M09 broker.mjs:1048 (run renamed) :174 entry shape 174, 205
M10 broker.mjs:1048 (from renamed) :174 entry shape 174, 205
M11 broker.mjs:1048 (at dropped) :174 entry shape 174
M12 broker.mjs:1053 (stranded key removed) :58 key list 58, 174, 189, 203, 221, 239
M13 broker.mjs:932 (role session rows) :62 role session keys 62
M14 broker.mjs:1048 (routed, not received) :239 routed, not yet received 239
M15 broker.mjs:1052 (.map removed) :174 prototype check 174

Verdicts: baseline green, manifest clean after restore, all 15 KILLED.

M12 note: the L58 key-list assertion does catch M12, at agents.test.mjs:58:32. The run also shows TypeErrors at :203 and :221, because the test reads stranded as undefined in those assertions. The first failing test header in that log is :49, with 6 of 11 tests failing (ℹ fail 6; the 13 ✖ lines are each failure twice plus the "failing tests:" header). The kill is real and the expected killer is correct. The mechanism is partly TypeErrors, not only the L58 check.

Console follow-up

Scope: read-only research of packages/webui and packages/cli. No code changed. Line numbers are from the current worktree.

Not checked, listed once here: a live read of /api/bus/agents (no run was made); the bus-browser test run; the contents of the "Not shown here yet" gap list at the end of agentsView; whether packages/webui references stranded anywhere (I did not grep for it, and the render functions below do not use it); whether #/messages/<id> exists as a detail route (grep found only list pages); the text of decision 123 in the repo; which session type (human or reader) the Console's humanCall opens; and whether the human-cli agents verb passes broker rows through unchanged.

(a) Which read carries stranded to the Console

  • Route: packages/webui/src/serve.mjs:52 (BUS_VERBS includes agents); busRead at L54; the GET-only /api/bus/ route at L223-227 returns 404 for unknown verbs.
  • Transport: packages/webui/src/bus.mjs:27 humanCall runs the human transport packages/bus/src/human-cli.mjs (header L1-2; lead decision 70).
  • Broker: packages/bus/src/broker.mjs:932 returns #agentState(c) rows when s.human || s.reader, and claim rows otherwise. #agentState at L1053 returns { ...c, state, working, inputNeeded, stranded }. The stranded query is at L1047-1052.
  • Result: for a human or reader session, the broker already puts stranded on the agents read. No read-path change is needed for the Console to receive it, subject to the unchecked items above. bus.test.mjs:77 already asserts that inputNeeded comes through the reader view. stranded sits on the same object, so the same path applies, but no test covers it yet.
  • CLI: packages/cli/src/cli.mjs:188 prints agents through formatAgents. packages/cli/src/format.mjs:52-55 prints role, holder run, harness and since. packages/cli/src/app.mjs:433 prints the detail lines (held by, harness, since). None of these read inputNeeded or stranded (grep of packages/cli/src for both names: no matches). A cli change is optional and not needed for the Console.

(b) Where a list or count would render, and what inputNeeded does

  • Render path: packages/webui/src/public/bus.js. fetchRead(verb, subject) at L34, and the get helper at L59-62.
  • attention(c) at L421-428 is the existing inputNeeded renderer. It links the first ask to #/inbox/<id>, shows a blocking chip, and shows a "+N" link to #/inbox.
  • heldTable(claims) at L435 builds the table. paintHeld(err) at L440-460 draws the Board held count, the refusal banner and the table. agentsView(get) at L462-470 renders the Held roles table with columns Role, State, Attention, Holder, Harness and Held since.
  • The Board refresh at L706-717 reads inbox, agents and messages, then calls paintHeld.
  • Natural fit for a follow-up: a stranded count in the Attention cell, built the same way as attention(c), with a link per message. The link target would be #/messages/<id>, which is not confirmed to exist (see unchecked list). A separate "Stranded" column is the alternative.

(c) What the follow-up row would test, and public/

  • Existing test patterns to extend: packages/webui/tests/bus.test.mjs:77 (reader-view inputNeeded assertion) and packages/webui/tests/bus-browser.test.mjs:248-259 (browser check of the Agents table: headers, state mark, "Input needed:" text, #/inbox/ link).
  • A follow-up would need, at minimum: stranded entries present in the reader rows; rendered count or links; the link target; no change to the read when nothing is stranded; and a check that listing writes nothing (no delivery row changes).
  • Public path: any render change touches packages/webui/src/public/bus.js, and any stylesheet under public/ would too. Per decision 123 that needs Mos's go. The read-path files (serve.mjs, bus.mjs) and the cli files are not under public/.

Open items

  1. Not committed, not pushed, queue row not moved.
  2. Sage to review the corrected M12 note above.
  3. Console follow-up is research only. It needs a separate queue row, and Mos's go for any public/ change.
  4. Phase A (superseded): base 2b2a9e14, 14 mutants, manifest sha256 4c91e42d71ff9a95e336db57f6c5821f3ef94f6e3dbf07ef860d71145d664c8a, broker hash cc949cea…. Superseded by the broker hash above.