Orchestrata Ultimate › Plans › briefs › qwen › Q8-report
Q8 report — hooks that judge a process by the words in its command line
Source: plans/briefs/qwen/Q8-report.md at 8a2cb42ad · last changed 2026-10-12 · first committed 2026-10-12 · 431 lines.
Prose pending.
Plan text
Q8 report — hooks that judge a process by the words in its command line
Worktree /home/mohamed/workspace/wt-HOOK-MATCH, branch fix/hook-process-matching,
HEAD 762935ab6. Nothing committed; changes staged, scratch (.q8/) unstaged.
This report covers both Q8-hook-process-matching.md and Q8-continuation.md.
The Qwen executor's session ended on the dispatcher timeout before writing this
report; the continuation §2 work (the leaking live_run_tree) had not been
done at all, so it was done here, red first. Deviations found in the inherited
work are listed under "Deviations".
1. Every copy and registration of the reaper
git ls-files | grep -i reap-orphan:
.claude/hooks/reap-orphan-waiters.sh
plugins/orchestrata/project-hooks/reap-orphan-waiters.sh
grep -rn reap-orphan-waiters .claude/settings*.json claude/ plugins/:
.claude/settings.json:46: "command": "cd \"$CLAUDE_PROJECT_DIR\" && bash .claude/hooks/reap-orphan-waiters.sh"
plugins/orchestrata/hooks/hooks.json:170: "command": "bash \"${CLAUDE_PLUGIN_ROOT}\"/project-hooks/_dispatch.sh reap-orphan-waiters.sh"
Two tracked copies, two registrations (SessionStart in both). The plugins copy
is generated by claude/scripts/gen_plugin.py; it was regenerated, never
hand-edited:
$ .venv/bin/python claude/scripts/gen_plugin.py
gen-plugin: hooks: 58 file(s) (0 written, 0 removed)
...
gen rc=0
$ .venv/bin/python claude/scripts/gen_plugin.py --check
gen-plugin: plugin is current under /home/mohamed/workspace/wt-HOOK-MATCH
check rc=0
diff between each source and its generated copy: empty
(REAPER-COPIES-IDENTICAL, CONTEXT-COPIES-IDENTICAL).
2. Part 1 (reaper) — requirement to implementing line
All line numbers in .claude/hooks/reap-orphan-waiters.sh (identical in the
generated copy):
- R1 candidate = shell running a loop payload.
/proc/<pid>/cmdlineread NUL-separated: lines 71–73 (mapfile -d ''). Shell basename, leading-stripped,bash|sh|dash|zsh: lines 75–80. Payload = element after the first^-[A-Za-z]*c[A-Za-z]*$element: lines 82–96. Shape tests on the payload alone: lines 98–101. - R2 orphan = adopted. Adopter set
$ORPHAN_WAITER_ADOPTER_PIDS, default1: lines 48, 54. ppid = field 4 of/proc/<pid>/statparsed after the last): lines 109–113. Membership check: lines 115–122. - R3 test seam
$ORPHAN_WAITER_ONLY_PIDS: lines 49, 56–62; documented as a test seam in the header, lines 34–37. - R4 age gate (
ORPHAN_WAITER_MIN_AGE_MIN, default 30): lines 47, 124–127. Never-this-hook/its-parent exclusions: lines 66–67, 103–104. - R5 kill =
pkill -9 -Pthenkill -9: lines 129–131. Log linekilled pid <pid> (ppid <ppid>) after <N>s: <payload, 90 chars>: lines 132–133. - R6 header rewritten to state R1–R3 and cite the 2026-10-11 incident: lines 2–8 (incident), 19–37 (R1–R3); "Never gates the session: always exits 0" kept at line 44.
3. Part 2 (status hook) — requirement to implementing line
In claude/hooks/test_run_context.py (identical in
plugins/orchestrata/hooks/test_run_context.py):
-
S1
_is_pytest(argv: list[str]) -> bool, lines 121–146; the docstring cites the §1.3 incident (pid 930286 reported for 53 minutes). Whitespace elements skipped (same rule as_is_authoritative), basenamepytest/py.test,-mfollowed bypytest, or-mpytest. -
S2
_runs()uses_is_pytest(_argv(pid))in place of_PYTEST_RE.search(cmd): lines 312–314._PYTEST_REafterwards:$ .venv/bin/python scripts/serialize_codebase_ast.py refs _PYTEST_RE USE claude/hooks/test_run_context.py:55 (Store) '_PYTEST_RE': 0 def, 0 import, 1 use(s).The single use is the definition itself (Store at line 55); no remaining reader. Per brief rule 9 it is left in place and reported here by name:
_PYTEST_REhas no remaining reference. -
S3 nothing else changed:
git diff HEAD -- claude/hooks/test_run_context.pyshows two hunks only (the_is_pytestaddition and the_runs()swap).
4. Tests — red first, then green
Red evidence (pre-fix code), files in .q8/
-
T1
test_an_orphaned_non_shell_quoting_a_loop_is_not_reaped— red inrun-red-t123.json(totals{'passed': 0, 'failed': 3, 'errors': 0, 'skipped': 0}):tests/scripts/test_reap_orphan_waiters.py:264: in test_an_orphaned_non_shell_quoting_a_loop_is_not_reaped E AssertionError: the reaper killed a non-shell process for text its argv only quotes; stdout='[reap-orphan-waiters] killed pid 183 after 0s: /home/mohamed/workspace/wt-HOOK-MATCH/.venv/bin/python -c \nimport ctypes, os, select, shle\n...' -
T2
test_a_waiter_whose_parent_is_alive_is_not_reaped— red inrun-red-t123.json:tests/scripts/test_reap_orphan_waiters.py:304: in test_a_waiter_whose_parent_is_alive_is_not_reaped E AssertionError: the reaper killed a waiter whose parent is alive; stdout="[reap-orphan-waiters] killed pid 209 after 0s: bash -c bash -c 'until test -f /nonexistent-998a...; do sleep 5; ..." -
T3
test_an_orphaned_shell_loop_past_the_age_gate_is_reaped— red inrun-red-t123.jsonon the R5 log-line shape (the old reaper also killed the adopter helper, pid 235, instead of the orphan):tests/scripts/test_reap_orphan_waiters.py:336: in test_an_orphaned_shell_loop_past_the_age_gate_is_reaped assert f"killed pid {pid} (ppid {helper.pid})" in result.stdout E AssertionError: assert 'killed pid 237 (ppid 235)' in '[reap-orphan-waiters] killed pid 235 after 0s: ...' -
T4/T5/T6 — red in
run-red-reaper.json({'passed': 3, 'failed': 7}) andrun-red-reaper2.json({'passed': 4, 'failed': 6}); e.g. T5:tests/scripts/test_reap_orphan_waiters.py:367: in test_only_pids_scopes_the_sweep E AssertionError: the reaper reached past ONLY_PIDS; stdout='[reap-orphan-waiters] killed pid 434 after 0s: ...'(the pre-fix reaper has no ONLY_PIDS seam and no orphan check, so every scoped/orphan-gated assertion fails).
-
P1
test_a_dispatcher_whose_prompt_quotes_pytest_is_not_a_run— red inrun-red-context.json({'passed': 28, 'failed': 1}):tests/claude/test_test_run_context.py:735: in test_a_dispatcher_whose_prompt_quotes_pytest_is_not_a_run E AssertionError: a process that merely QUOTES pytest in its argv was reported as a pytest run -
P2, P3 are not marked "red today" in the brief (the old regex matched real runs too); they pin the narrowing and pass pre- and post-fix.
-
Continuation
test_live_run_tree_leaves_no_child_behind— red against the unfixed fixture (the fixture body factored into_live_run_tree()but still killing only the parent),.q8/run-red-livetree.txt:tests/claude/test_test_run_context.py:155: in test_live_run_tree_leaves_no_child_behind assert not Path(f"/proc/{child}").exists(), f"child {child} outlived the fixture" E AssertionError: child 1997012 outlived the fixture 1 failed, 1 warning in 5.83s [hostlock] 05:12:16 rc=1The red is a real leak, not a zombie artifact: pid 1997012 was still alive (
ls /proc/1997012listed the full entry) after the run and was killed by hand (kill -9 1997012; thenred-run child 1997012 reaped).
Green evidence (post-fix)
-
Module green after the continuation fix,
.q8/run-green-livetree.txt:30 passed, 1 warning in 3.38s [hostlock] 05:13:22 rc=0 -
Authoritative forced run (Verification 1),
.q8/run-q8.json:test_totals: {'passed': 1545, 'failed': 0, 'errors': 0, 'skipped': 0} reused: {'modules': 0, 'tests': 0} modules: 97 — nonzero modules: [] run rc=0The four brief-named modules inside it:
tests/scripts/test_reap_orphan_waiters.py {'passed': 10, 'failed': 0, 'errors': 0, 'skipped': 0} tests/claude/test_test_run_context.py {'passed': 30, 'failed': 0, 'errors': 0, 'skipped': 0} tests/claude/test_test_run_context_coverage.py {'passed': 13, 'failed': 0, 'errors': 0, 'skipped': 0} tests/scripts/test_session_start_hooks.py {'passed': 28, 'failed': 0, 'errors': 0, 'skipped': 0}(The earlier Qwen-run green,
run-green.json, covered only the two edited modules — 39 passed — and is superseded by the run above.)
5. The replaced test (T2 authorisation)
test_a_matching_waiter_past_the_age_gate_is_reaped at HEAD asserted the
defect — a waiter whose parent (the live test process) is alive is killed
anyway, and the reaper ran unscoped over the whole host:
result = _run(min_age_min="0")
assert result.returncode == 0
deadline = time.time() + 15
while time.time() < deadline and proc.poll() is None:
time.sleep(0.5)
assert proc.poll() is not None, (
"the reaper left an orphaned waiter alive past its age gate; "
f"stdout={result.stdout!r}"
Authorising paragraph (brief §2, Tests):
T2 replaces
test_a_matching_waiter_past_the_age_gate_is_reaped. That test asserts the defect of §1.1 (a waiter with a live parent is killed), against the hook's own header. The orchestrator authorises this one contract change. In the report, quote the old test's assertion and this paragraph.
6. T7 — the four pre-existing tests
An AST-level comparison of the assertion expressions, HEAD vs working tree:
test_the_reaper_never_gates_the_session
HEAD: assert _run().returncode == 0
new: assert _run(str(sleeper.pid)).returncode == 0
test_a_young_waiter_is_left_alone
HEAD: assert proc.poll() is None, 'fixture loop exited on its own' / 'the reaper killed a young waiter'
new: assert _alive(pid), 'fixture loop exited on its own' / 'the reaper killed a young waiter'
test_a_loop_without_sleep_or_empty_body_is_not_matched
HEAD: assert proc.poll() is None, 'the reaper killed a working while-read loop'
new: assert _alive(pid), 'the reaper killed a working while-read loop'
test_hook_is_registered_at_session_start
UNCHANGED
The only expression changes are the ones T7 itself mandates: every reaper
invocation now passes ONLY_PIDS (naming the fixture pid the test started),
and each fixture became a helper-adopted orphan, so the process handle
(proc.poll()) became a /proc liveness read (_alive(pid)) on the adopted
pid. Assertion messages and the gates they name (session-exit, age, shape,
registration) are identical; each fixture fails the gate it names, not the
orphan check (adopter set names the helper). Full diff:
git diff HEAD -- tests/scripts/test_reap_orphan_waiters.py.
7. Continuation §2 — the leaking live_run_tree
- Factored, not replaced. The fixture body moved to the module-level
@contextlib.contextmanager_live_run_tree(); the fixture keeps its name, scope and yield value ({"parent": ..., "child": ...}— the child pid was already exposed) and is nowwith _live_run_tree() as tree: yield tree. No existing test or assertion changed. - Red first: §4 above (
child 1997012 outlived the fixture, rc 1). - The fix inside
_live_run_tree(): the parent starts withstart_new_session=True; teardown isos.killpg(parent.pid, signal.SIGKILL)(ProcessLookupErrortolerated),parent.wait(timeout=10), then a 5 s poll of/proc/<child_pid>that raisesAssertionErrorif the entry remains — a leak is a red result, not a silent one. foreign_runchecked (continuation §2.4): it spawns ONE detached process (python -c 'import time; time.sleep(120)' scripts/run_tests.py), which spawns no grandchildren; teardown SIGKILLs that pid and polls/proc/<pid>to absence (_await_proc(pid, present=False)). The_spawn_detachedfixtures behind P1–P3 are the same shape (one process, killed and awaited in_kill_detached); the P2/P3 pytest runs execute the sleeper in-process. No other fixture in the touched files spawns grandchildren, solive_run_treewas the only one needing the group kill.- Module green after the fix:
30 passed, 1 warning in 3.38s(quote in §4), and again inside the authoritative run (30 passed intest_test_run_context.py).
8. Verification 1 — the forced run
Targets (.q8/q8-targets.txt, 97 modules): the four brief-named modules plus
the affected_tests.py set. The affected-set computation:
$ env -u PYTHONPATH .venv/bin/python scripts/affected_tests.py --json --files <six changed files>
"escalate": false
"tests": ["tests/claude", "tests/claude/test_run_detection_ignores_watchers.py",
"tests/claude/test_test_run_context.py", "tests/claude/test_test_run_context_coverage.py",
"tests/infrastructure", "tests/scripts/test_reap_orphan_waiters.py",
"tests/scripts/test_session_start_hooks.py"]
"untested": []
escalate is false, so no whole-suite STOP. Directory entries
(tests/claude, tests/infrastructure) were expanded to their 95 test_*.py
modules; no tests/conftest.py entry was present.
The run (detached, rule 9/10 form
env -u PYTHONPATH -u PYTEST_ADDOPTS bash .run_exclusive.sh --force --json <97 targets>):
test_totals: {'passed': 1545, 'failed': 0, 'errors': 0, 'skipped': 0}
reused: {'modules': 0, 'tests': 0}
run rc=0 time_seconds: 67.1 head_commit: 762935ab6176b3231bf49a413850e52d4e995760
0 failed, 0 errors, 0 skipped, reused 0, rc 0.
9. Verification 2 — host-safety proof
Immediately before the run:
$ pgrep -fa '(while|until)[[:space:]]' > .q8/loops-before.txt
$ wc -l < .q8/loops-before.txt
0
Immediately after:
$ pgrep -fa '(while|until)[[:space:]]' > .q8/loops-after.txt
$ wc -l < .q8/loops-after.txt
0
Result: the before-file lists zero pids, so zero foreign pids could
disappear, and none did; the after-file is empty, so none of the (while|until)
fixture loops my tests started (T2–T4, T6, Verification 3) leaked past the
run. (The orchestrator's waiters the brief expects in the list were already
gone: the dispatcher's 90-minute timeout ended that session before this
executor started.)
10. Verification 3 — the incident, end to end
bash /tmp/opencode/q8-v3-repro.sh (scoped:
ORPHAN_WAITER_MIN_AGE_MIN=0 ORPHAN_WAITER_ONLY_PIDS="<both pids>"), full
output saved to .q8/v3-repro.txt:
DISPATCH=2000231 MID=2000232 CHILD=2000234
--- fixtures before the reaper ---
PID PPID COMMAND
2000231 2000230 .venv/bin/python -c import time; time.sleep(120) timeout 3600 bash -c 'while pgrep -f "scripts/run_tests[.]py" >/dev/null; d
2000232 2000230 bash -c bash -c "until test -f /nonexistent-q8-incident-repro; do sleep 5; done" & echo $! > /tmp/opencode/q8-v3-child; wait
2000234 2000232 bash -c until test -f /nonexistent-q8-incident-repro; do sleep 5; done
--- ORPHAN_WAITER_MIN_AGE_MIN=0 ORPHAN_WAITER_ONLY_PIDS="2000231 2000234" bash .claude/hooks/reap-orphan-waiters.sh ---
hook rc=0
--- both alive after the reaper? ---
PID PPID STAT ELAPSED COMMAND
2000231 2000230 S 1 .venv/bin/python -c import time; time.sleep(120) timeout 3600 bash -c 'while pgrep -f "scripts/run_tests[.]py" >/dev/null; do sleep 15; d
2000232 2000230 S 1 bash -c bash -c "until test -f /nonexistent-q8-incident-repro; do sleep 5; done" & echo $! > /tmp/opencode/q8-v3-child; wait
2000234 2000232 S 1 bash -c until test -f /nonexistent-q8-incident-repro; do sleep 5; done
ps rc=0 (0 = all three listed = alive)
cleanup kill rc=0
all fixtures gone after cleanup
The reaper printed no killed line: the non-shell quoting a loop (the
930286 shape) and the waiter with a live parent (the 931060/931062 shape)
both survived, and the fixtures were then killed by the script itself.
11. Orphan check (continuation §3)
After the forced run:
$ for p in $(pgrep -f -- '-m pytest'); do [ "$(awk '{print $4}' /proc/$p/stat 2>/dev/null)" = 1 ] && readlink /proc/$p/cwd; done | grep -c wt-HOOK-MATCH
0
Zero -m pytest orphans with ppid 1 and this worktree as cwd. (grep -c
exits 1 on a zero count; the 0 is the pass.)
12. Verification 4 — lint, gates, staging
bash -n both hook copies: rc 0
shellcheck both hook copies: rc 0
ruff check (4 Python files): All checks passed!
black --check (4 Python files): 4 files would be left unchanged
pyright (4 Python files): 0 errors, 0 warnings, 0 informations
check-deps --include-tests: rc 0 — "✅ All third-party imports are declared in pyproject.toml."
gen_plugin.py: 0 written; --check rc 0 ("plugin is current")
gate.py run needed one environment repair first, documented under
Deviations (D4). After it:
gate: PASS
- 5 rule clause(s), no violations
fact base: 426,459 facts across 23 predicates
gate rc=0
Staged: the six changed files (both hook copies, both test_run_context.py
copies, both test modules), the two Q8 brief files, and this report. Never
staged: .q8/, reports/ (reports/run-tests-latest.json was touched by
the test runs and stays unstaged), plans/ast-*.json (see D4 — modified by
the refresh, deliberately unstaged), .hostlock.sh, .run_exclusive.sh.
scripts/gate.py review-staged: see the final message / staged diff.
13. Deviations found in the inherited (Qwen) work, and what was done
-
D1 — continuation §2 was not implemented. The
live_run_treefixture still killed only the parent; there was no_live_run_tree()context manager, nostart_new_session=True/killpgteardown, and notest_live_run_tree_leaves_no_child_behind. Done here, red first (§4, §7). -
D2 — Verification 1 covered only 2 modules.
run-green.jsonrantests/scripts/test_reap_orphan_waiters.pyandtests/claude/test_test_run_context.py(39 passed). Re-run here over the 97-module target set (§8). -
D3 — Verification 3 output was not on disk. Re-run here; output saved to
.q8/v3-repro.txt(§10). -
D4 — HEAD's committed
plans/ast-ultimate.jsondescribes another worktree. Itsroot_pathis/home/mohamed/workspace/wt-GRAPH-DEFECTS/src/orchestrata_ultimate(identical at HEAD —git diff HEADon it is empty), sogate.py runexited 2 with six stale findings, all of themwt-GRAPH-DEFECTSpaths:gate: INVALID - fact base does not describe the working tree: 6 file(s) across 6 finding(s). Re-run: uv run ast-sync (then scripts/build_runtime_callgraph.py reaugment) stale (6): /home/mohamed/workspace/wt-GRAPH-DEFECTS/src/orchestrata_ultimate/kernel/pipeline_escalation.py (content_changed) ... 5 moreNone of the six is a Q8 file; the failure predates this task. The tool-prescribed, AGENTS.md-canonical refresh (
env -u PYTHONPATH .venv/bin/python scripts/run_ast_pipeline.py --snapshot-only, rc 0; newroot_path= this worktree) was run, after whichgate.py runisPASS, rc 0 (quote in §12). The refreshedplans/ast-ultimate.jsonand derivedplans/ast-tests.jsonare left modified but UNSTAGED, exactly as the staging rules require.
14. BLOCKED items
None.