Orchestrata Ultimate › Plans › briefs › qwen › Q8-hook-process-matching

Q8 — hooks that judge a process by the words in its command line

Source: plans/briefs/qwen/Q8-hook-process-matching.md at 8a2cb42ad · last changed 2026-10-12 · first committed 2026-10-12 · 271 lines.

Prose pending.

Plan text

Q8 — hooks that judge a process by the words in its command line

DESIGN_WAIVER: none needed. Both changes make a hook do what its own header already says it does. Neither adds a capability.

Worktree: /home/mohamed/workspace/wt-HOOK-MATCH (branch fix/hook-process-matching, from origin/master 762935ab6). Work ONLY there.

0. Rules (binding)

  1. A red test blocks completion, whoever owns it. Every failed, errored or skipped test needs a root cause with evidence (traceback, line) and either a fix or a STOP naming the cause. "Outside my files", "pre-existing", "flaky", "infra" and "unrelated" are not statuses. STOP means blocked, not done.
  2. No mocks, stubs, fakes, monkeypatch, skip, xfail, or -n/xdist. Real processes, real /proc, real signals.
  3. Do NOT commit, merge, push, checkout, restore, reset, stash or clean. git add/show/diff/status/log are allowed. The orchestrator commits.
  4. Read Python through scripts/serialize_codebase_ast.py symbols|source|refs where it serves. Reading a file you are about to edit is fine.
  5. Absolute imports. os.environ.get(...). Typed signatures and docstrings.
  6. Never write the word "should". Every number in the report comes from a command run in this session, quoted next to it.
  7. A guard denial is reported, never worked around. Do not reshape a denied command (brackets, quoting, splitting) so that it passes.
  8. No test may signal a process it did not start. Every reaper invocation in a test passes ORPHAN_WAITER_ONLY_PIDS (R3 below) naming only that test's fixture pids. This host runs other agents whose waiters are exactly the shape the reaper hunts; an unscoped run kills them.
  9. Do not remove a function or constant. If a change leaves one without a reference, report it by name; the orchestrator decides.

0.1 Running tests

Other agents run suites on this host, and two runs share worker databases and ports. Run tests only through the wrapper the orchestrator placed in your worktree root (do not edit or stage it):

mkdir -p .q8
bash .run_exclusive.sh --force --json <module> ... > .q8/run-<label>.json 2> .q8/run-<label>.err; echo "run rc=$?"

It takes a host-wide lock (flock), waits for any run started outside the lock, then runs. --force means every selected test executes and no green is reused. Exit 124 means the wait exceeded 90 minutes: STOP and report. Do not write your own pgrep wait loop. .q8/ is scratch, never staged.

1. Evidence (orchestrator, 2026-10-11)

1.1 The reaper killed a live executor

.claude/hooks/reap-orphan-waiters.sh runs at SessionStart (compaction included). At 20:55:12 it printed:

[reap-orphan-waiters] killed pid 930286 after 3148s: timeout 5400 env -i HOME=/home/mohamed USER=mohamed LOGNAME=mohamed LANG=C.UTF-8 TERM=dumb
[reap-orphan-waiters] killed pid 931060 after 3132s: /bin/bash -c timeout 3600 bash -c 'while pgrep -f "scripts/run_tests[.]py" >/dev/null; do
[reap-orphan-waiters] killed pid 931062 after 3132s: bash -c while pgrep -f "scripts/run_tests[.]py" >/dev/null; do sleep 15; done

1.2 Who adopts an orphan on this host

Measured: bash -c 'sleep 30 & echo $!; exit 0', then ps -o ppid= -p <that pid> → 1 (/proc/1/comm = systemd). The orchestrator's own process chain is bash → claude → bash → konsole → systemd(1): no subreaper in it.

1.3 The status hook counts a dispatcher as a pytest run

claude/hooks/test_run_context.py _runs() decides "authoritative run" by argv element (_is_authoritative, which skips any element containing whitespace, so a quoted pattern or a -c payload never counts). It decides "other pytest process" by _PYTEST_RE.search(cmd) over the joined command line (_PYTEST_RE = re.compile(r"\bpytest\b|-m\s+pytest")). For 53 minutes on 2026-10-11 it reported pid 930286, an opencode dispatcher whose prompt contains the word pytest, as 1 targeted pytest process(es) running: pid 930286 · wt-BARE-PYTHON.

2. Work

Part 1 — the reaper (.claude/hooks/reap-orphan-waiters.sh)

First, git ls-files | grep -i reap-orphan and grep -rn reap-orphan-waiters .claude/settings*.json claude/ plugins/ 2>/dev/null. Report every copy and registration. Change each tracked copy of the hook the same way. A generated copy is regenerated by its generator, never hand-edited.

Contract after the change:

Part 2 — the status hook (claude/hooks/test_run_context.py)

Tests, red first

Write each test first, run it, and quote its red output before changing the code. Each fixture process is started by the test and killed in finally or a function-scoped fixture's teardown, whatever happened.

tests/scripts/test_reap_orphan_waiters.py:

tests/claude/test_test_run_context.py:

Verification

  1. Through §0.1, label q8: tests/scripts/test_reap_orphan_waiters.py, tests/claude/test_test_run_context.py, tests/claude/test_test_run_context_coverage.py, tests/scripts/test_session_start_hooks.py, plus the env -u PYTHONPATH .venv/bin/python scripts/affected_tests.py --json --files <every changed file> set. If the affected set escalates to the whole suite, do not run it; report it. Pass: 0 failed, 0 errors, 0 skipped, reused.modules 0. Quote test_totals and reused.

  2. Host-safety proof. Before and after the q8 run, record every process on the host matching (while|until)[[:space:]]: pgrep -fa '(while|until)[[:space:]]' > .q8/loops-before.txt and > .q8/loops-after.txt. Report that no pid in the before-file that your tests did not start disappeared during the run. The orchestrator's own waiters are in that list on purpose.

  3. The incident, end to end. Outside pytest, by hand:

    1. start a helper-free reproduction of §1.1: a python process whose argv element quotes the wait loop, and under a live intermediate a real shell waiter;
    2. run ORPHAN_WAITER_MIN_AGE_MIN=0 ORPHAN_WAITER_ONLY_PIDS="<both pids>" bash .claude/hooks/reap-orphan-waiters.sh;
    3. show both alive; kill them yourself afterwards.

    Quote the commands and output.

  4. bash -n and shellcheck (if command -v shellcheck finds it) on the hook; ruff check + black --check + pyright on changed Python; scripts/gate.py run; scripts/serialize_codebase_ast.py check-deps --include-tests; git add your changes (never .q8/, .run_exclusive.sh, reports/); scripts/gate.py review-staged. All exit 0.

3. Done-when

STOP conditions (write the report, then stop):