Skip to content

fix: contain runaway test and lab process trees - #66

Merged
MrGTV-love merged 6 commits into
mainfrom
fm/fm-proc-budget-wrapper
Oct 9, 2026
Merged

MrGTV-love merged 6 commits into
mainfrom
fm/fm-proc-budget-wrapper

Conversation

@MrGTV-love

@MrGTV-love MrGTV-love commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Intent

I would like all bugs to be fixed so tomorrow can be focused entirely to Vernant and not problems preventning Vernant from getting built.

A recommended architecture should come with proof.

Context (the backlog record for this bug, as filed):

Per-tree process budget for tests, labs and measurement scripts (repo: firstmate) (kind: ship) (since 2026-10-08)
Report section 8.1 (+8.4 optional): add bin/fm-proc-budget.sh -- <command...> that sets ulimit -u to (current user process count + extra, default 1500) for the child tree only. Use it in bin/fm-test-run.sh where each test script command is built, in the lab creators/runners (coordinate with in-flight fm-lab-orphan-reaper, which touches them), and add a one-line rule to the worker brief scaffold: run any shim, lab or measurement script through fm-proc-budget.sh. Proof: the report's leashed reproduction (bomb stopped at +376, no failure outside the tree) as a behavioral test. Optional 8.4: rerun a drain once on exit 128 + fork EAGAIN text + no WAKE_ACK line.

Source: data/fm-fork-eagain-diagnosis/report.md (2026-10-08). Cause of the fleet-wide fork EAGAIN 17:14-17:23: worker fm-board-listener-retire built a self-recursive test shim (basename linked to a shim that calls basename) that piled ~10,300 processes against kern.maxprocperuid 10,666; lanes d3/d5, Main and workers lost drains (macOS bash 3.2 exits 128 on one failed fork). Rate fixes (fork-budget, lint bound, drain-fast) do not stop a pile-up.

What Changed

  • Add fm-proc-budget.sh to cap child-tree process limits at the current user count plus configurable headroom (default 1500), without widening inherited limits; refuse execution when a budget cannot be set.
  • Apply process budgets to test scripts and lab launches, add the wrapper rule to worker briefs and measurement instructions, and add regression coverage including a leashed recursive shim with an unbudgeted fork probe.
  • Make Claude auto-arm report explicit failed supervision-host hand-backs without retrying merely because output is empty, and update failure-handling documentation and regression coverage.

Risk Assessment

✅ Low: The changes are narrowly scoped, preserve tighter inherited budgets and existing ownership controls, and introduce no substantiated source defect or intent contradiction.

Testing

Live wrapper, recursive-shim, isolated test-runner, generated-brief, and real tmux launch checks passed. The runaway stopped at depth 352 while the outside probe completed 1,059 forks without stderr. Targeted wrapper, fake-Herdr, and auto-arm regressions passed; transcripts, generated-output evidence, and incident diagnostics were retained. No complete repository suite, lint, static analysis, or real Herdr lifecycle ran, and all disposable fixtures were removed.

  • Live validation: ✅ go - 9 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Run a measurement command with default or explicit headroom and inherit sealed limits without changing the parent ✅ pass live wrapper-live.txt: explicit and default limits were numeric, soft equaled hard, descendants inherited them, and parent limits remained 10666/16000.
Wrap a command without changing its PID, arguments, or exit status ✅ pass live wrapper-live.txt: exec PID matched the launching PID, spaced and literal wildcard arguments survived, and child exit 7 propagated.
Nest a larger budget and attempt to raise the sealed limit ✅ pass live wrapper-live.txt: outer and nested limits remained 1684/1684; raising the soft limit was refused.
Supply malformed arguments, unavailable process counting, or legacy bypass settings without running unbudgeted ✅ pass live wrapper-live.txt records exit 2 or 125 with no command marker; wrapper-no-bypass.txt records sealed limits despite legacy settings; obsolete modes exited 2.
Run the recursive basename shim and preserve fork capacity outside its budgeted tree ✅ pass live incident-live.txt: inside limits were 1898/1898, recursion stopped at depth 352 with Resource temporarily unavailable, and the outside probe completed 1059 forks with exit 0 and empty stderr.
Execute a measurement script through the real test runner and inherit the budget through its timeout and CPU-pass launch path ✅ pass live runner-live.txt: script and Python descendant both reported limits 3051/3051; the runner completed successfully.
Make process counting fail and have the test runner refuse script execution ✅ pass live runner-fail-closed.txt: wrapper refusal was reported, script exit was 125, runner exit was 1, and no measurement output or persisted execution marker appeared.
Generate ship and scout briefs and execute their budget command from ordinary and quoted installation paths ✅ pass live brief-live.txt: all three ship modes and scout emitted identical rules for each installation path; every emitted command executed and reported sealed limits.
Launch a real private tmux lab through the shipped launcher and remove it after observing inherited budgets ✅ pass live tmux-lab-live.txt: the shipped lab_run function launched a real private server; pane and descendant inherited 2967/2967 versus the outside soft limit 10666; helper teardown and removal confirmed the l…
Provision a real named Herdr lab and viewer with budgets, then perform guarded teardown ⏸️ untested no The recorded decision expressly prohibits real Herdr lifecycle and default-session access for this brief, accepts fake-Herdr coverage plus exec PID preservation, and defers live proof. No real Herdr c…
Have a real Claude Stop dispatch notify on an explicit failed host hand-back instead of retrying ⏸️ untested no The hook regression used fake harness and host dependencies, so it is not live Claude evidence. This gate checkout is a linked worktree where the primary-only hook stays inert; the authorized disposab…
Evidence: Wrapper limits, propagation, nesting, and refusals

Source: Wrapper limits, propagation, nesting, and refusals

$ bash ~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/wrapper-scenarios.sh
parent soft=10666 hard=16000; baseline user processes=    1545
extra=80: 1629 1629
descendant soft/hard=(1629, 1629)
observed allowance=84; user processes after=    1535
extra=default: 3040 3040
descendant soft/hard=(3040, 3040)
observed allowance=1495; user processes after=    1544
exec pid=12570 expected=12570 args=<a b>|<c*>
child exit propagated=7
outer soft=1684 hard=1684
nested soft=1684 hard=1684
raising sealed hard limit refused
fm-proc-budget: extra must be a positive decimal integer, got '0'
malformed extra=0 exit=2 command-ran=no
fm-proc-budget: extra must be a positive decimal integer, got '01'
malformed extra=01 exit=2 command-ran=no
fm-proc-budget: extra must be a positive decimal integer, got '-5'
malformed extra=-5 exit=2 command-ran=no
fm-proc-budget: extra must be a positive decimal integer, got '1.5'
malformed extra=1.5 exit=2 command-ran=no
fm-proc-budget: extra must be a positive decimal integer, got 'nope'
malformed extra=nope exit=2 command-ran=no
fm-proc-budget: cannot read the process count, so no budget can be set
unreadable count exit=125 command-ran=no
parent remains soft=10666 hard=16000

[exit=0; elapsed=4.75s]
Evidence: Leashed recursive-shim incident and unaffected outside probe

Source: Leashed recursive-shim incident and unaffected outside probe

$ bash ~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident-live.sh
outside parent soft=10666 hard=16000
runaway tree exit=127; outside probe exit=0
inside soft=1898 hard=1898
outside probe completed 1059 successful forks
runaway maximum recursion depth=352
runaway fork diagnostics:
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: fork: Resource temporarily unavailable
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
outside probe stderr=''

[exit=0; elapsed=29.25s]
Evidence: Complete runaway-tree fork diagnostics

Source: Complete runaway-tree fork diagnostics

~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: fork: Resource temporarily unavailable
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-pr

... [41705 bytes truncated] ...

638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/basename: line 6: exec: : not found
~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/incident/bomb/date: line 6: exec: : not found
Evidence: Real test-runner measurement limits

Source: Real test-runner measurement limits

$ bin/fm-test-run.sh --jobs 1 --per-script-timeout-secs 15 --json ~/.no-mistakes/evidence/01M4G01FPCZHEAXED5ZRXC923P/runner-live.json tests/fm-budget-live.test.sh
FM_TEST_BEGIN 2026-10-09T13:56:44Z tests/fm-budget-live.test.sh family=unclassified expected_gate_skip=none
measurement script pid=52284 soft=3051 hard=3051
measurement descendant pid=52306 soft=3051 hard=3051
FM_TEST_END 2026-10-09T13:56:44Z tests/fm-budget-live.test.sh exit=0 duration_ms=836 gate_skip=false
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0 duration_ms=1280
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=836 failed=0
FM_TEST_SLOWEST rank=1 script=tests/fm-budget-live.test.sh duration_ms=836
fm-test-run: wrote timing artifact: ~/.no-mistakes/evidence/01M4G01FPCZHEAXED5ZRXC923P/runner-live.json

[exit=0; elapsed=1.56s]
Evidence: Test runner refuses unbudgeted execution

Source: Test runner refuses unbudgeted execution

$ bin/fm-test-run.sh --jobs 1 --per-script-timeout-secs 15 --json ~/.no-mistakes/evidence/01M4G01FPCZHEAXED5ZRXC923P/runner-refused.json tests/fm-budget-live.test.sh
FM_TEST_BEGIN 2026-10-09T13:56:45Z tests/fm-budget-live.test.sh family=unclassified expected_gate_skip=none
fm-proc-budget: cannot read the process count, so no budget can be set
FM_TEST_END 2026-10-09T13:56:46Z tests/fm-budget-live.test.sh exit=125 duration_ms=417 gate_skip=false
FM_TEST_SUMMARY total=1 failed=1 skipped_gate=0 duration_ms=897
FM_TEST_SUMMARY_FAMILY family=unclassified count=1 duration_ms=417 failed=1
FM_TEST_SLOWEST rank=1 script=tests/fm-budget-live.test.sh duration_ms=417
fm-test-run: wrote timing artifact: ~/.no-mistakes/evidence/01M4G01FPCZHEAXED5ZRXC923P/runner-refused.json

[exit=1; elapsed=1.35s]
Evidence: Generated brief rules and execution of their quoted commands

Source: Generated brief rules and execution of their quoted commands

mode=no-mistakes quoted_path=False
9. Run any shim, lab, or measurement script through `'~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/bin/fm-proc-budget.sh' -- <command...>`: a runaway process tree then stops at its own limit instead of starving every lane of fork.
emitted command executed: soft=2994 hard=2994


mode=direct-PR quoted_path=False
9. Run any shim, lab, or measurement script through `'~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/bin/fm-proc-budget.sh' -- <command...>`: a runaway process tree then stops at its own limit instead of starving every lane of fork.
emitted command executed: soft=3052 hard=3052


mode=local-only quoted_path=False
9. Run any shim, lab, or measurement script through `'~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/bin/fm-proc-budget.sh' -- <command...>`: a runaway process tree then stops at its own limit instead of starving every lane of fork.
emitted command executed: soft=3083 hard=3083


mode=scout quoted_path=False
9. Run any shim, lab, or measurement script through `'~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/bin/fm-proc-budget.sh' -- <command...>`: a runaway process tree then stops at its own limit instead of starving every lane of fork.
emitted command executed: soft=3027 hard=3027


mode=no-mistakes quoted_path=True
9. Run any shim, lab, or measurement script through `'~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/budget helper'\''s root/bin/fm-proc-budget.sh' -- <command...>`: a runaway process tree then stops at its own limit instead of starving every lane of fork.
emitted command executed: soft=3026 hard=3026


mode=direct-PR quoted_path=True
9. Run any shim, lab, or measurement script through `'~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/budget helper'\''s root/bin/fm-proc-budget.sh' -- <command...>`: a runaway process tree then stops at its own limit instead of starving every lane of fork.
emitted command executed: soft=3031 hard=3031


mode=local-only quoted_path=True
9. Run any shim, lab, or measurement script through `'~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/budget helper'\''s root/bin/fm-proc-budget.sh' -- <command...>`: a runaway process tree then stops at its own limit instead of starving every lane of fork.
emitted command executed: soft=3016 hard=3016


mode=scout quoted_path=True
9. Run any shim, lab, or measurement script through `'~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/budget helper'\''s root/bin/fm-proc-budget.sh' -- <command...>`: a runaway process tree then stops at its own limit instead of starving every lane of fork.
emitted command executed: soft=3056 hard=3056
Evidence: Real private tmux budget inheritance and teardown

Source: Real private tmux budget inheritance and teardown

$ bash ~/.no-mistakes/worktrees/32d18ed9638d/01M4G01FPCZHEAXED5ZRXC923P/.gate-proc-budget-validation/tmux-lab-live.sh
/tmp/fm-lab-01M4FNQD-dOgCmR
outside lab soft=10666 hard=16000
real tmux server pid=9544 private socket=/tmp/fm-lab-01M4FNQD-dOgCmR/tmux
pane pid=9571 soft=2967 hard=2967
descendant pid=9610 soft=2967 hard=2967
pane and real descendant inherited identical sealed budgets below the outside limit
budget report completed







































removed lab and private socket directory: /tmp/fm-lab-01M4FNQD-dOgCmR (absent)

[exit=0; elapsed=1.48s]

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 7 issues found → auto-fixed ✅
  • ⚠️ bin/fm-brief.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-proc-budget.sh - merge conflict rebasing onto origin/main
  • ⚠️ bin/fm-test-run.sh - merge conflict rebasing onto origin/main
  • ⚠️ docs/scripts.md - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-brief.test.sh - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-proc-budget.test.sh - merge conflict rebasing onto origin/main
  • ⚠️ tests/fm-test-run.test.sh - merge conflict rebasing onto origin/main

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Review** - passed

✅ No issues found.

✅ No issues found.

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 8 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Run a budgeted command with explicit or default headroom while preserving its identity, arguments, and exit status ✅ pass live wrapper-live.json: explicit limits 1816/1816, command PID matched the launched PID, spaced argument preserved, exit 7 propagated; default limits were 2967/2967.
Nest a wrapper inside a tighter budget and attempt to raise the sealed limit ✅ pass live wrapper-live.json: outer and inner limits remained 1836/1836; attempted escalation exited 1.
Submit malformed budget arguments and observe refusal before the command runs ✅ pass live wrapper-live.json: zero, negative, fractional, nonnumeric, and leading-zero extras exited 2; the command marker was never created.
Make process counting unavailable and observe wrapper refusal rather than unbudgeted execution ✅ pass live wrapper-live.json: actual wrapper with an injected failing ps exited 125, reported an unreadable process count, and did not execute the command.
Launch the recursive basename shim and keep an unbudgeted sibling forking during exhaustion ✅ pass live bash tests/fm-proc-budget.test.sh: the real runaway stopped at depth 326 before its safety ceiling, encountered Resource temporarily unavailable, and the outside probe completed without a fork failure…
Run a script through the real test runner and observe budgeting inside a CPU pass, including fail-closed behavior ✅ pass live runner-live.json: fixture reported soft=2987 hard=2987 held=1; failing ps produced script exit=125 and runner exit=1 without creating the execution marker.
Generate every worker brief mode and execute its budget command, including a root containing spaces and an apostrophe ✅ pass live brief-live.json and generated brief artifacts: all three ship modes and scout emitted the same rule and executable quoted command; every execution reported sealed numeric limits.
Launch real private tmux panes through the lab launcher, observe inherited budgets, and remove the disposable lab ✅ pass live tmux-lab-live.json: actual lab_run launched a 120x40 private server; both panes reported 3058/3058, including a pane started through an unbudgeted client. Escalation failed, all recorded processes exi…
Provision a real named Herdr lab and viewer, observe budgets, and perform guarded teardown ⏸️ untested no The latest recorded user decision explicitly forbids real Herdr lifecycle and even default-session reads because this brief is not Herdr-lab enabled. Only the authorized fake-Herdr route was exercised…
  • Scoped the submitted change with git diff --stat and the targeted base-to-target diff.
  • Measured parent limits using ulimit -S -u and ulimit -H -u, and counted real user processes.
  • Drove bin/fm-proc-budget.sh 300 -- bash -c ... to observe sealed limits, preserved PID, intact arguments, and child exit status.
  • Exercised default and nested budgets, attempted limit escalation, rejected malformed extras, and injected a failing ps dependency.
  • Ran bash tests/fm-proc-budget.test.sh, including the real leashed recursive-shim reproduction and concurrent unbudgeted fork probe.
  • Ran bin/fm-test-run.sh --jobs 1 --per-script-timeout-secs 20 --json &lt;fixture-output&gt; &lt;disposable-script&gt; with an isolated real CPU pool; repeated with failing ps.
  • Generated briefs using bin/fm-brief.sh for no-mistakes, direct-PR, local-only, and scout modes; executed their emitted wrapper commands from normal and space/apostrophe-containing roots.
  • Created /tmp/fm-lab-01M4FNQD-u6x17fgr using bin/fm-lab-home.sh create; loaded the actual lab_run function and launched private tmux with -L fm-lab -f /dev/null -x 120 -y 40.
  • Captured two real tmux panes, inspected server/pane process identities, killed only the private server, ran bin/fm-lab-home.sh teardown, removed the lab, and confirmed its processes and directories were gone.
  • Ran bash tests/fm-herdr-lab.test.sh against its stateful fake Herdr only.
  • Saved command transcripts and generated briefs, then removed all disposable worktree fixtures.

✅ No issues found.

  • Live validation: ✅ go - 9 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Run a command with default or explicit headroom and observe count-plus-extra limits ✅ pass live wrapper-transcript.json: measured default limit 2964 from count 1464, and explicit limit 1601 from count 1501 plus 100.
Wrap a command without changing its arguments, PID, exit status, or caller limits ✅ pass live wrapper-transcript.json: quoted arguments preserved, exit 7 preserved, launcher and child PID both 31681, caller limits remained 10666/16000.
Nest wrappers and attempt to raise the sealed process limit ✅ pass live wrapper-transcript.json: nested soft/hard limits both 1566; raising the limit returned 1. Removed environment aliases did not disable budgeting.
Submit malformed calls or deny process-count access and verify the child never runs ✅ pass live wrapper-transcript.json: malformed calls returned 2; denied process-count access returned 125; child marker was absent.
Run the recursive basename shim and preserve forks outside its bounded tree ✅ pass live leashed-reproduction.txt: recursion stopped at depth 316 below safety valve 1500, with Resource temporarily unavailable inside the tree; the independent unbudgeted probe completed without errors. The…
Execute serial and concurrent test workloads and observe descendant budget inheritance ✅ pass live runner-transcript.json: real workloads and descendants inherited equal soft/hard limits below 10666, including concurrent execution with CPU passes and a timeout.
Deny the runner's process-count access and prevent unbudgeted workload execution ✅ pass live runner-transcript.json: script exit 125, aggregate exit 1, explicit process-count refusal, and no WORKLOAD output.
Generate ship and scout briefs and execute their budget commands from ordinary and quoted paths ✅ pass live worker-brief-rules.json: all four worker modes emitted executable commands; roots containing spaces and an apostrophe executed successfully.
Launch the authorized disposable tmux lab, observe inherited limits, and remove its state ✅ pass live tmux-lab-transcript.txt: the shipped lab_run function launched real tmux; pane and descendant reported soft=hard=2939 below caller soft=10666; helper teardown and removal confirmed lab and private soc…
Provision a real named Herdr lab and viewer with inherited budgets and guarded teardown ⏸️ untested no The recorded captain decision explicitly prohibits real Herdr lifecycle and default-session inspection for this brief, retains fake-Herdr coverage, and files live proof as follow-up. That authority bo…
Have a real Claude Stop lifecycle notify a failed hand-back without retrying it while retaining bounded crash retries ⏸️ untested no The exercised regressions use a fake Claude ancestor and host fixtures, so they are not live harness evidence. The inspected hook requires a genuine primary and owning session; this run's out-of-workt…
  • command -v bash python3 tmux claude codex omp; caller soft/hard limit baseline and post-execution checks.
  • Direct bin/fm-proc-budget.sh executions covering default and explicit budgets, measured count arithmetic, nested limits, argument quoting, exit status, exec-preserved PID, malformed calls, and denied process-count access.
  • /bin/bash tests/fm-proc-budget.test.sh.
  • /bin/bash .fm-budget-validation/leashed-reproduction.sh: evidence-producing recursive-shim containment with a concurrent unbudgeted fork probe.
  • Isolated real fm-test-run.sh executions: serial workload; two concurrent workloads with --jobs 2 --per-script-timeout-secs 30; denied process-count access.
  • test_each_script_runs_under_a_process_budget from tests/fm-test-run.test.sh.
  • Real fm-brief.sh generation and execution of emitted wrapper commands for no-mistakes, direct-PR, local-only, and scout modes, including a helper path containing spaces and an apostrophe.
  • test_crewmate_scaffolds_route_scratch_scripts_through_the_process_budget and test_process_budget_command_quotes_foreign_firstmate_path from tests/fm-brief.test.sh.
  • /bin/bash .fm-budget-validation/tmux-lab-proof.sh: helper-created authorized lab, shipped lab_run function, private tmux socket, pane/descendant observations, helper teardown, and removal proof.
  • /bin/bash tests/fm-herdr-lab.test.sh: existing fake-Herdr coverage only.
  • test_host_failed_handback_is_not_retried and test_host_crash_is_retried_then_reported from tests/fm-claude-stop-autoarm.test.sh: mocked regression coverage only.
  • Removed .fm-budget-validation and confirmed the authorized tmux lab path was absent.

✅ No issues found.

  • Live validation: ✅ go - 9 of 11 scenarios driven live against the product
Scenario Result Live Evidence
Run a measurement command with default or explicit headroom and inherit sealed limits without changing the parent ✅ pass live wrapper-live.txt: explicit and default limits were numeric, soft equaled hard, descendants inherited them, and parent limits remained 10666/16000.
Wrap a command without changing its PID, arguments, or exit status ✅ pass live wrapper-live.txt: exec PID matched the launching PID, spaced and literal wildcard arguments survived, and child exit 7 propagated.
Nest a larger budget and attempt to raise the sealed limit ✅ pass live wrapper-live.txt: outer and nested limits remained 1684/1684; raising the soft limit was refused.
Supply malformed arguments, unavailable process counting, or legacy bypass settings without running unbudgeted ✅ pass live wrapper-live.txt records exit 2 or 125 with no command marker; wrapper-no-bypass.txt records sealed limits despite legacy settings; obsolete modes exited 2.
Run the recursive basename shim and preserve fork capacity outside its budgeted tree ✅ pass live incident-live.txt: inside limits were 1898/1898, recursion stopped at depth 352 with Resource temporarily unavailable, and the outside probe completed 1059 forks with exit 0 and empty stderr.
Execute a measurement script through the real test runner and inherit the budget through its timeout and CPU-pass launch path ✅ pass live runner-live.txt: script and Python descendant both reported limits 3051/3051; the runner completed successfully.
Make process counting fail and have the test runner refuse script execution ✅ pass live runner-fail-closed.txt: wrapper refusal was reported, script exit was 125, runner exit was 1, and no measurement output or persisted execution marker appeared.
Generate ship and scout briefs and execute their budget command from ordinary and quoted installation paths ✅ pass live brief-live.txt: all three ship modes and scout emitted identical rules for each installation path; every emitted command executed and reported sealed limits.
Launch a real private tmux lab through the shipped launcher and remove it after observing inherited budgets ✅ pass live tmux-lab-live.txt: the shipped lab_run function launched a real private server; pane and descendant inherited 2967/2967 versus the outside soft limit 10666; helper teardown and removal confirmed the l…
Provision a real named Herdr lab and viewer with budgets, then perform guarded teardown ⏸️ untested no The recorded decision expressly prohibits real Herdr lifecycle and default-session access for this brief, accepts fake-Herdr coverage plus exec PID preservation, and defers live proof. No real Herdr c…
Have a real Claude Stop dispatch notify on an explicit failed host hand-back instead of retrying ⏸️ untested no The hook regression used fake harness and host dependencies, so it is not live Claude evidence. This gate checkout is a linked worktree where the primary-only hook stays inert; the authorized disposab…
  • bash .gate-proc-budget-validation/wrapper-scenarios.sh: default and explicit budgets, descendant inheritance, unchanged parent limits, exec PID preservation, arguments, exit status, nesting, invalid inputs, and unreadable process counts.
  • bash tests/fm-proc-budget.test.sh: targeted wrapper regression, including the leashed recursive shim.
  • bash .gate-proc-budget-validation/incident-live.sh: recursive basename shim alongside an unbudgeted fork probe, with complete diagnostics retained.
  • Ran an unchanged disposable copy of bin/fm-test-run.sh --jobs 1 --per-script-timeout-secs 15 --json &lt;evidence-path&gt; tests/fm-budget-live.test.sh through a workspace-local CPU-pass pool; repeated with a failing ps dependency.
  • Executed bin/fm-brief.sh for no-mistakes, direct-PR, local-only, and scout modes; executed each emitted budget command, including paths containing spaces and an apostrophe.
  • bash .gate-proc-budget-validation/tmux-lab-live.sh: minted the authorized lab with fm-lab-home.sh create, launched real tmux through the shipped fm-live-lab.sh launch function, observed pane and descendant limits, killed the private server, ran helper teardown, and confirmed removal.
  • bash tests/fm-herdr-lab.test.sh: fake-client server/viewer budget and guarded-cleanup regression coverage; no real Herdr calls.
  • bash tests/fm-claude-stop-autoarm.test.sh: supplemental fixture-driven regression coverage, including silent failed hand-backs and crash retries.
  • Confirmed legacy bypass environment variables do not disable budgeting and obsolete --count and --check modes exit 2.
  • Removed all newly created worktree fixtures and confirmed the authorized external lab was absent.
✅ **Document** - passed

✅ No issues found.

✅ No issues found.

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ No issues found.

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

✅ No issues found.

✅ No issues found.

@MrGTV-love
MrGTV-love force-pushed the fm/fm-proc-budget-wrapper branch from 5bc54a2 to 244c67c Compare October 9, 2026 12:31
@MrGTV-love MrGTV-love changed the title fix: bound process growth in tests and labs fix: budget script processes and report failed host hand-backs Oct 9, 2026
The kernel compares the process limit with the user's whole process count,
so one runaway scratch tree (a shim whose basename is itself) filled the
table and cost every lane its forks. bin/fm-proc-budget.sh sets the limit to
the current count plus a headroom (default 1500) for one command tree only.
bin/fm-test-run.sh starts each script under it, bin/fm-live-lab.sh starts
every lab process under it, and the crewmate brief scaffolds carry a rule
that routes ad-hoc shim, lab, and measurement scripts through it.
…rial 7. Empty host output incorrectly triggered a crash retry before an explicit hand-back failure was classified, allowing the replacement host to park indefinitely. Removed that retry condition while preserving bounded retries for signal exits. Added behavioral coverage for empty/nonempty output with and without a live successor, and updated the supervision documentation. The regression failed before the fix. Afterward, both fm-claude-stop-autoarm and fm-supervision-host-hook suites passed through the budgeted runner with no skips. A targeted real-host smoke also confirmed notification without retry when readiness output was absent. ShellCheck with sourced dependencies and Bash syntax checks passed. Temporary fixtures were removed
@MrGTV-love
MrGTV-love force-pushed the fm/fm-proc-budget-wrapper branch from 244c67c to dab28e7 Compare October 9, 2026 14:37
@MrGTV-love MrGTV-love changed the title fix: budget script processes and report failed host hand-backs fix: contain runaway test and lab process trees Oct 9, 2026
@MrGTV-love
MrGTV-love merged commit 933312f into main Oct 9, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant