7.8 KiB
Row 47, S5 follow-up (Mr flows test and the shim leak), round 1 review (Darkwing)
Issue #1533, request comment 27036, notes comment 27037, queue revs 277
and 278 (10351f6a). Brief: docs/plans/2026-10-10_s5-follow-up-and-hygiene.md
(blob 24ceb88c), section "S5 follow-up". Base 21e0f1a3. Candidate
manifest sha256
0587e393746e5cc6bb31e06b94461661fc7679f6f5fa6a72835c60b286a6055f,
6 files: five test files in packages/conversation/tests/ and Dewey's
evidence.md. No src/ change.
Verdict: approve, comment 27043. The new flows test is the shape of my row 40 note 1
probe in both chunkings, and it's the only test Mr fails. The leak
diagnosis checks out against the code: the shim ignores SIGTERM, nothing in
src/ sends release, and two claim tests left a force-stopped scope for
an after hook whose kill results nobody checked. Each test now reaps its own
scope, every scope-launching file ends with a check that names a leak, and
the after hook fails the file if a shim survives the sweep. Every leak
mutant I tried in claim, cohort and races is killed, and afterleak
reproduces the round 2 leak with the hook catching it. Two notes below,
neither blocking.
Method
- A detached worktree at
21e0f1a3with the six files copied from a snapshot of the canonical tree, thensha256sum -c: 6 OK. Checked again after the mutants: 6 OK (r1/mut/manifest-after.txt). - I read the diff in full,
src/shim.mjs,ScopeLauncherandforceStopCohortinsrc/cohort.mjs,#runinsrc/terminal.mjs, and W5, W13, K19, H22 and H23. - 10 mutants (
r1/mut/mutate.py,run.sh), each run on the whole conversation suite with its own TMPDIR. After each run the runner lists any shim left under that TMPDIR, SIGKILLs itsmosaic-chat-*scope and the PID, and restores the files from backup copies. - No shim was running before the gate, after it, or after any mutant other
than
afterleak(killed by the runner, see below). After all runssystemctl --user list-units --all 'mosaic-chat-*'lists nothing.
Node is v26.8.1, TMPDIR=~/darkwing-scratch/r47a/tmp. gate.sh set
DOCKER_HOST=unix:///nonexistent.sock.
Suites
| Suite | Result |
|---|---|
| conversation | 165/0 |
| webui | 22/0 |
| test-auth | 15/0 |
| test-conductor | 17/0 |
| test-config | 24/0 |
| test-discord | 66/0 |
| test-extension-package | 18/0 |
| test-foundation | 44/0 |
| test-queue | 27/0 |
| test-release | 4/0 without Docker, 14/0 with it (test-release-docker.txt) |
| test-task | 26/2 |
The two test-task failures are "user recall run succeeds" and "recalled
user name", the live worker check that needs Docker and a model call. They
fail the same way in my row 41 gates. Dewey's run reports 98/0, so it ran
the Docker cases; I didn't.
Mr
The test types Ctrl-T, Ctrl-O inside the text held behind it, hi Enter,
then yo Enter and Ctrl-C, once as three chunks and once as one. It
asserts sent is ["hi"], then held is null, then yo goes through and
Ctrl-C quits. That's my probe/stacked-later.mjs from row 40, plus the
single-chunk case, where the hold and the stacked hold come from one
#parse. Under Mr (holds = false; at terminal.mjs:158) the suite is
164/1 and the one failure is this test (r1/mut/Mr-conversation.txt).
The leak
Dewey's reading matches the code:
shim.mjsignores SIGTERM and SIGHUP and exits only onreleaseonce the engine cgroup is empty.- The only shim ops
src/sends arehello,term,events,freeze,membersandkill(cohort.mjs:115-192). So a force-stopped scope stays up with its shim until something SIGKILLs the unit. - W5's probe and W13 force-stop a scope and left it to the
afterhook, whosesystemctl --user killandstopresults were never checked.
The evidence names f55, the W5 probe fixture, as Filbert's leaked shim. My
afterleak run leaked the same fixture (f55/sock/shim-cede1ee324e5.sock).
Dewey says what the journal can't show, which is why the after
kill missed in Filbert's run. The fix doesn't depend on knowing: the tests
reap right away, and the sweep kills by PID whatever systemctl does.
The new helpers read /proc/<pid>/cmdline for node <...>/shim.mjs --socket <path under tmp>, which is exactly ScopeLauncher's argv
(cohort.mjs:105). tmp is per test process, so each file sees only its
own shims, and a parallel gate from another seat isn't touched. noprobe
failing proves the detector finds a real shim.
K19's finally used to assert on kill before sending release, so a
failed kill left the scope. It now sends release only after a good kill,
SIGKILLs the scope if either fails, and asserts afterwards.
Mutants
| Mutant | Change | Conversation | Result |
|---|---|---|---|
| Mr | holds = false; after re-parsing held text |
164/1 | killed by the new flows test |
| noprobe | W5 doesn't reap its probe | 164/1 | killed: claim's "no shim ... left running" |
| now13 | W13 doesn't reap its fixture | 164/1 | killed, same test |
| afterleak | noprobe, and after doesn't reap or sweep |
164/2 | killed: the same test and claim's after hook; 1 shim left, killed by the runner |
| sweeponly | noprobe, and after sweeps but doesn't reap |
164/1 | killed by the last test; the sweep alone cleans up, no shim left |
| sweeppid | sweeponly, and killShims skips the scope kill |
164/1 | same; the PID kill alone is enough |
| k19leak | K19's finally sends no kill and no release |
164/1 | killed: cohort's "no shim ... left running" |
| h22noreap | H22 (force stop, then relaunch in a scope) doesn't reap | 164/1 | killed: races' "no shim ... left running" |
| h23noreap | H23 doesn't reap | 165/0 | equivalent: H23 runs on pgroup, no shim |
| reapnosweep | reap() without its killShims(fx.base) |
165/0 | survives, expected: it's a backstop for a missed systemctl |
afterleak is the round 2 leak on purpose. Its hook failure names the shim
(r1/mut/afterleak-conversation.txt:205), and node:test marks the file
failed:
AssertionError [ERR_ASSERTION]: no shim outlives this file (#1533)
+ pid: 3459119,
+ socket: '.../tmp/mut-afterleak/chat03-lQNQDH/f55/sock/shim-cede1ee324e5.sock',
+ unit: 'mosaic-chat-cede1ee324e5b263ef2671152'
So all three scope-launching files have their final check killed by a leak mutant, which Dewey's table showed only for claim.
Notes (not blocking)
liveShimstakes the first cgroup segment ending in.scopeas the shim's unit, andkillShimsSIGKILLs that whole unit. Today every shim comes fromScopeLauncher, so that's themosaic-chat-*scope. A future test that spawnedshim.mjsdirectly from a shell inside a terminal'sapp-*.scopewould get that terminal's scope killed. Matching/^mosaic-chat-.*\.scope$/, which is what my runner does, costs one regex. Worth folding into the next change toharness.mjs.- The brief asks for the Mr output in the packet.
evidence.mdhas the table and theafterleakexcerpt; the outputs stay in~/dewey-scratch. Mine are inr1/mut/if Sage wants them on file.
Finding for Sage
I agree with Dewey's out-of-scope finding and the recommended follow-up. In
a running controller every force stop leaves a mosaic-chat-*.scope with
an idle node shim, until the unit is killed by hand or the user manager
restarts. The fix is release after a proven force stop (and on close of a
stopped binding), with a test that the unit is gone. That's cohort.mjs or
controller.mjs work, outside this brief.
Files
r1/candidate-manifest.sha256,r1/files.txt: copies of Dewey's.r1/gate.sh,r1/out/: suite runs,summary.txtwith a shim count after each node suite and at the end.r1/mut/: mutant definitions, runner, per-mutant output and shims-left lists,summary.txt, and the manifest check after the runs.h22noreapran after the first batch, so its line followsDONE.