Skip to content

fix(tests): one bound, one place β€” the 120s subprocess cap reddened the gate on diffs that could not reach it - #1493

Merged
ZacxDev merged 6 commits into
mainfrom
fix/run-tests-targets-subprocess-bound
Sep 11, 2026
Merged

ZacxDev merged 6 commits into
mainfrom
fix/run-tests-targets-subprocess-bound

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Sep 11, 2026

Copy link
Copy Markdown
Member

Closes the second gate-flake family, documented as rank 7 in
handoff-gate-flake-store-api.md (talos-infra#1477). That doc diagnosed it; this fixes it β€”
at the cause, not only at the symptom.

The defect

run-tests.sh skips the hook-test and shell-test families on a narrowed run β€” but the skip
was keyed on SCOPED_MODE, which only --files sets. So a --targets run executed both
families in full, and no --targets value can name either.

scripts/tests/test_run_tests_targets.py spawns nested --targets runs under a subprocess
bound. Those runs were ~12Γ— more expensive than the work they selected, and under CI
contention they breached the bound β€” failing tekton/devrc-pytests on PRs whose diff cannot
reach either file. Three occurrences: #1450 (a SKILL.md), #1458, #1462 (a handoff
doc). #1458 and #1462 merged over the red; #1450 is still open.

Measured

invocation before after
--targets scripts/collector/i3/tests (13 tests) 47 s (load ~52) 9 s (load ~46)
--files <one file in it> 4 s 4 s

Under bash -x, of ~54 s: the selected target's pytest was 2.9 s; ~46 s was the two
families (test_base_clone_staleness.sh 19.3 s, test_diagnose_disk_accounting.sh 13.2 s,
test_bash_guard.py 8.7 s).

Mechanism reproduced first-hand, by lowering the bound rather than waiting for CI:
subprocess.TimeoutExpired, returncode -9, traceback ending in _check_timeout β€” and
therefore not an assertion failure, which is why it sent readers to debug their own diff.

The change

scripts/run-tests.sh β€” the family skip is re-keyed from SCOPED_MODE to
SCOPE_STATE != FULL, so any narrowed run drops them.

πŸ”΄ The skip is announced on the PARTIAL surface too. Widening the skip without widening the
announcement would be a run silently executing two families fewer than its own SUMMARY claims
β€” the #276 shape this runner exists to refuse.

⚠ It does not drag the rest of SCOPED_MODE along. A PARTIAL run still enforces every
per-target floor, GUARD 7's REQUIRED direction and GUARD 2's skip TOTAL, and still reports
SCOPE: PARTIAL. That is asserted β€” reusing SCOPED_MODE wholesale would have passed the
skip assertions while silently suspending the floors.

scripts/testlib/scoped_harness.py β€” RUNNER_TIMEOUT_S = 300 now lives here, the module
whose run() already carried its own timeout=600 default across five test files. A constant
private to one test file would have been a fourth copy of the predicate.

scripts/tests/test_run_tests_targets.py β€” six open-coded timeout=120 sites collapse
into one _spawn(), which translates TimeoutExpired into a failure stating the
classification ("not an assertion failure, probably not your diff"). Plus an AST guard
against a seventh site.

Regression matrix

RED at origin/main's runner β€” the failure output is itself the proof, showing
PASS scripts/claude-hooks/tests/test_claude_notify.py (script) inside a --targets run β€”
GREEN at HEAD. The pre-existing test_a_full_run_still_runs_the_hook_and_shell_families
is the positive control that a FULL run is unaffected.

Guard mutation-tested

Control green. Each mutant killed by the assertion that claims it, read from the E lines
only:

mutant outcome
7th raw subprocess.run KILLED
import subprocess as sp / from subprocess import run as _r KILLED
Popen(...).communicate(), unbounded KILLED
getoutput / getstatusoutput (no timeout param exists) KILLED
bound respelled literal / deleted entirely KILLED
spawn delegated out of the file (vacuous pass) KILLED

The last two matter most: a check that only inspected the timeout= kwargs it found, or
that accumulated over calls it found, passes hardest on the worst outcome.

Audit ladder

Round 0 (requirements/deletion) β€” challenged this PR's own premise and is why the root
cause is fixed here rather than filed. Round 1 (blind, nine axes) β€” three 🟑, three 🟒, all
fixed; it caught that my cost paragraph named the wrong CI cap and inverted the consequence,
that the guard could pass vacuously, and that "one rule, one place" was file-local.

Known-incomplete, stated rather than implied

  • test_run_tests_preconditions.py (300) and test_devshell_satisfies_required_tools.py
    (120 Γ—2) still carry their own bounds. They drive fast precondition/PATH-stub aborts, so
    none is near its bound β€” this is about the thesis, not a live defect.
  • The AST guard is file-scoped and cannot see them.
  • os.system, from subprocess import * and assignment aliasing are deliberately not
    covered; the comment enumerates rather than claiming closure.

…he gate on diffs that could not reach it

`test_run_tests_targets.py` spawns nested `run-tests.sh` runs under a hard
subprocess timeout. That bound was 120s, open-coded at SIX call sites, and it
has been failing PRs whose diff cannot touch this file β€” devrc#1458 and #1462
both merged with `tekton/devrc-pytests` RED because of it.

MEASURED, not inferred:

- Mechanism reproduced first-hand by lowering the constant: `subprocess.
  TimeoutExpired`, returncode -9, traceback ending in `_check_timeout`. It is
  NOT an assertion failure, which is exactly why it sent readers to debug
  their own diff.
- One nested run of `scripts/collector/i3/tests` β€” the SMALLEST target, 13
  tests β€” took 47s on the dev host at load ~52, i.e. 39% of the old bound,
  and almost all of it is the runner's fixed preflight rather than the tests.
  CI is documented at 27-50 concurrent full-suite runs on one node.

So the bound was breached by CONTENTION, not by anything a diff did.

Three changes:

1. `_RUNNER_TIMEOUT_S = 600` β€” one named constant carrying the measurement and
   the reasoning. ~12x the measured figure, still bounding a genuine hang well
   inside the gate's budget. Deliberately not env-overridable.
2. `_spawn()` β€” the single place a nested run is bounded. All six sites route
   through it. It TRANSLATES `TimeoutExpired` into a failure that states the
   classification ("this is not an assertion failure and probably not your
   diff"), because a bare TimeoutExpired names a command and a number and
   tells the reader nothing about which is wrong.
3. `test_no_call_site_open_codes_its_own_subprocess_bound` β€” pins the
   RELATIONSHIP, parsed via AST rather than grepped.

Guard mutation-tested; control green (32 passed), each hazard killed by the
assertion that claims it, naming line and function:

  A  7th raw subprocess.run in _run()          KILLED  "without going through `_spawn`"
  B  aliased `from subprocess import run as _r` KILLED  same (a regex walks past this)
  C  _spawn bound respelled literal 120        KILLED  "timeout is Constant(value=120)"
  D  _spawn bound deleted entirely             KILLED  "timeout is ABSENT"

D matters most: a check that only inspected the `timeout=` kwargs it FOUND
would pass hardest on the unbounded hang this bound exists to prevent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCmdWjiRn4PtLGTJrgmiX1
Claude-Session-Id: 460124dc-bb3d-4afb-b9e5-0687bec64e44
@ZacxDev

ZacxDev commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Merged-tree gate β€” both tiers green

origin/main b315cdd3 + this branch (fast-forward, no conflict). Sandbox tier β€” the one
Tekton runs β€” derivations built ONE AT A TIME:

  • pytests PASS β€” collected=22082 passed=22080 skipped=2 failed=0,
    SCOPE: FULL (28 of 28 hermetic target(s)), RESULT: PASS (exit=0), NIXBUILD_RC=0
  • nodetests PASS β€” TOTAL suites=5 files=41 tests=1449 pass=1449 fail=0 (floor 1367),
    RESULT: PASS (exit=0), NIXBUILD_RC=0

⚠ The dev-host tier (scripts/gate.sh) was not run.

⚠ What this green does NOT prove. The flake is contention-triggered, so a passing run is
one sample and cannot show the new bound is sufficient β€” only that 600 s was not breached on
this run, on a box whose load I did not control. The evidence for sufficiency is the
measurement in _RUNNER_TIMEOUT_S's comment (47 s for the smallest target at load ~52, ~12Γ—
headroom), not this pass. The claim I am making is "the bound is now derived from a
measurement and lives in one place", not "the flake is proven gone".

The thing that is proven is the regression direction: a seventh open-coded bound, or a
bound deleted, now fails by name β€” watched, in the table above.

…, and the cost is not "preflight"

Round-0 audit findings, all fixed.

1. The AST predicate was NARROWER THAN ITS OWN DOCSTRING β€” the defect class it
   exists to catch. It resolved `from subprocess import run as r` but NOT
   `import subprocess as sp; sp.run(...)`, a sibling spelling of the same
   aliasing the docstring claims to close; and it matched only `run`, so
   `Popen(...).communicate()` with no bound was invisible while the assertion
   beside it called exactly that "worse than the red it replaces". Both were
   measured SURVIVING. Now an enumerated `_SPAWNING_CALLABLES` plus resolution
   of module aliases and from-imports.

2. The constant's own comment said the 47s is "almost all the runner's fixed
   preflight rather than the tests". FALSE, and it is the load-bearing half: a
   `--targets` run still executes the hook-test and shell-test families, which
   `--targets` does not narrow. Of ~54s under `bash -x`, the SELECTED target's
   pytest is 2.9s and ~46s is those families. Discriminator, same box, same
   load ~52, minutes apart: `--targets <dir>` = 47s vs `--files <one file>` = 4s,
   because `--files` sets SCOPED_MODE and run-tests.sh:4518 drops the families.
   The comment now says so, names the root cause, and says this bound is the
   SYMPTOM fix.

3. The worst-case budget was asserted, not measured. Recorded: four real nested
   runs, so 4x600s = 40 min against a measured timeouts.tasks of 1h10m β€” 57% of
   the task budget, and a run that hits it posts NOTHING (checks pending
   forever). Stated as the trade this value makes.

4. Occurrence count was under-stated. #1450 is a third clean occurrence β€” a
   skills-doc-only diff red on a real-nested-run test in this file. #1429 is
   genuinely ambiguous (its diff touches run-tests.sh) and #1466 was a broken
   gate; both correctly excluded.

5. Dead code the previous commit propagated into new code: the `cwd` parameter
   no caller has ever passed, and an unused `import shutil`.

Mutation battery re-run on the widened predicate, control green (32 passed),
reading ONLY the `E ` lines:

  E  import subprocess as sp; sp.run(...)     KILLED  (SURVIVED before)
  F  Popen(...).communicate(), no bound       KILLED  (SURVIVED before)
  A  7th raw subprocess.run                   KILLED
  B  from subprocess import run as _r         KILLED
  C  bound respelled literal 120              KILLED  "timeout is Constant(value=120)"
  D  bound deleted entirely                   KILLED  "timeout is ABSENT"

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCmdWjiRn4PtLGTJrgmiX1
Claude-Session-Id: 460124dc-bb3d-4afb-b9e5-0687bec64e44
@ZacxDev

ZacxDev commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Round 0 β€” ran, and it changed the outcome

ran: 1 Β· changed the outcome: yes (round-0 trial ledger).
Verdict: requirement questioned β€” the 600 s bound; the 47 s it is derived from is ~90% work the runner already has a mode to skip.
Ledger: round 0 Β· requirements: 6 (unattributed: 2) Β· deletion candidates: 4.

πŸ”΄ Correction to this PR's description β€” posting rather than silently editing the body

The body says the 47 s is "almost all … the runner's fixed preflight rather than the tests."
That is wrong, and it is the load-bearing half.

It is not preflight. A --targets run still executes the hook-test and shell-test
families
, which --targets does not narrow. Under bash -x, of ~54 s: the selected
target's pytest is 2.9 s; test_base_clone_staleness.sh alone is 19.3 s,
test_diagnose_disk_accounting.sh 13.2 s, test_bash_guard.py 8.7 s.

I re-measured the discriminator myself rather than taking the audit's word β€” same box,
same load ~52, minutes apart:

invocation wall
--targets scripts/collector/i3/tests (real run) 47 s
--files <one file in that target> 4 s

~12Γ—. --files sets SCOPED_MODE, and run-tests.sh:4518 drops those families under it β€”
"these five were 101s of a measured 172s subset run", the runner's own comment.

So this PR is the symptom fix, and the constant now says so. The root cause is that a
--targets run pays for work it did not select, and the runner already contains the
machinery to skip it, reachable only via --files. Decoupling that takes the nested run to
~4 s and would make even the original 120 s ~30Γ— headroom. Calling the cost "preflight" is
exactly what made a bigger timeout look like the only lever.

πŸ”΄ And the guard was narrower than its own docstring

The defect class the guard exists to catch. Measured SURVIVING against the first version:

  • E β€” import subprocess as sp; sp.run(...). The docstring claims to close aliasing and
    named only the from-import spelling; _is_spawn_call required fn.value.id == "subprocess" literally.
  • F β€” subprocess.Popen(...).communicate() with no bound at all β€” the precise
    unbounded hang the neighbouring assertion calls "worse than the red it replaces".

Both now killed. Full battery re-run on the widened predicate, control green (32 passed),
reading only the E lines:

mutant before now killed by
E import subprocess as sp SURVIVED KILLED …without going through _spawn β€” line 198, in _run()
F Popen(...).communicate(), unbounded SURVIVED KILLED same, line 197
A 7th raw subprocess.run KILLED KILLED same, line 196
B from subprocess import run as _r KILLED KILLED same, line 197
C bound respelled literal 120 KILLED KILLED timeout is Constant(value=120)
D bound deleted entirely KILLED KILLED timeout is ABSENT

🟑 The worst-case budget was asserted, not measured β€” now it is

The body claimed 600 s "still bounds a genuine hang well inside the gate's own budget". I had
not measured that. devrc-ci-pipeline has timeouts.tasks = 1h10m0s, and everything runs in
one gate task. This file performs four real nested runs, so worst case moves from
4Γ—120 s = 8 min to 4Γ—600 s = 40 min β€” 57% of the task budget. A run that hits that cap
posts nothing and the checks stay pending forever (CLAUDE.md, measured on
devrc-ci-nnt6f/-9p6mf). The constant now records this as the trade it makes, rather than
the reassurance it previously offered.

Occurrence count was under-stated

#1450 is a third clean occurrence β€” diff is claude/skills/subsystem-index/SKILL.md
only, red on test_a_partial_run_is_declared_where_gate_sh_actually_LOOKS, a real-nested-run
test in this file. So: 3 clean (#1450, #1458, #1462), 1 ambiguous (#1429 β€” its diff does
touch run-tests.sh), 1 not-this (#1466 β€” BROKEN GATE: step clone failed rc 128).

Also deleted

The cwd parameter no caller has ever passed β€” which the previous commit propagated into
brand-new code β€” and an unused import shutil.

Still open, deliberately

The root-cause fix (a third runner mode: skip the families for --targets too) is not in
this PR. It is not a flag swap β€” SCOPED_MODE also suspends GUARD 3's whole-target floor,
GUARD 7 REQUIRED and GUARD 2's skip TOTAL, and renames the banner to SCOPE: SCOPED … via --files, which is the exact string two of the affected tests assert on. Raising it as a
decision rather than quietly doing it.

Also open and larger than this PR: 25 other files under scripts/ hardcode a 3-digit
subprocess bound and spawn run-tests.sh/gate.sh
β€” test_nogit_isolation.py (15 sites),
test_dead_guard_scan.py (14), test_gate_exit_truthfulness.py (5, mixing 120 and 300).
There is no repo-wide scanner. This PR closes the class for one file.

1. Finding 1 (the AST predicate was narrower than its docstring: `import subprocess as sp` and `Popen`/`check_output`/`call` were invisible; mutants E and F measured SURVIVING): claimed addressed by enumerating `_SPAWNING_CALLABLES` and resolving module aliases and from-imports separately; claimed re-verified by re-running the full six-mutant battery with control green and each mutant killed by the assertion that claims it, reading only the `E ` lines.
2. Finding 2 (the constant's comment said the 47s is "almost all the runner's fixed preflight rather than the tests", which is false): claimed corrected to name the hook-test and shell-test families, to carry the 47s-vs-4s discriminator re-measured independently at load ~52, and to state explicitly that this bound is the SYMPTOM fix and where the root cause lives.
3. Finding 3 (worst-case gate budget asserted, not measured): claimed addressed by recording four real nested runs x 600s = 40 min against a measured timeouts.tasks of 1h10m (57%), and that a run hitting that cap posts nothing, stated as the trade this value makes rather than as reassurance.
4. Finding 6 (occurrence count under-stated): claimed corrected to three clean occurrences β€” #1450 added, verified as a skills-doc-only diff red on a real-nested-run test β€” with #1429 and #1466 named as excluded and why.
5. Findings D1/D2 (dead `cwd` parameter propagated into new code; unused `import shutil`): claimed deleted.
6. NOT addressed, deliberately: the root-cause runner change (R2/D4), and the 25-file repo-wide class. Both named above as open decisions rather than silently dropped.

…nnot select β€” the root cause, not the bound

Operator chose to fix the cause in this PR rather than file it.

THE CAUSE. `run-tests.sh` drops the hook-test and shell-test families on a
`--files` run, keyed on SCOPED_MODE β€” which only `--files` sets. A `--targets`
run therefore executed BOTH families in full, and no `--targets` value can name
either. Re-keyed on `SCOPE_STATE != FULL`, so any narrowed run drops them.

MEASURED, dev host, same box:

    --targets scripts/collector/i3/tests   47 s  (before, load ~52)
    --targets scripts/collector/i3/tests    9 s  (after,  load ~46)

Under `bash -x`, of ~54 s the SELECTED target's pytest was 2.9 s and ~46 s was
those families. That ~12x is what breached the nested-subprocess bound in
test_run_tests_targets.py under CI contention.

πŸ”΄ The skip is ANNOUNCED on the PARTIAL surface too. Widening the skip without
widening the announcement would be a run silently executing two families fewer
than its own SUMMARY claims β€” the #276 shape. Both moved together.

⚠ It does NOT bring the rest of SCOPED_MODE: a PARTIAL run still enforces every
per-target floor, GUARD 7 REQUIRED and GUARD 2 TOTAL, and still reports
SCOPE: PARTIAL. Asserted, because reusing SCOPED_MODE wholesale would have
passed the skip tests and silently suspended the floors.

Regression matrix for the new test: RED at origin/main's runner (AssertionError;
its output shows `PASS scripts/claude-hooks/... (script)` β€” the family running
inside a --targets run), GREEN at HEAD. The pre-existing
test_a_full_run_still_runs_the_hook_and_shell_families is the positive control
that a FULL run is unaffected.

ROUND-1 AUDIT FIXES:

1. The bound now lives in testlib/scoped_harness.py, which ALREADY had its own
   `timeout=600` default used by five test files. A constant private to
   test_run_tests_targets.py was a FOURTH copy while claiming "one rule, one
   place". Both read RUNNER_TIMEOUT_S now. Still unconsolidated and said so:
   test_run_tests_preconditions.py (300) and
   test_devshell_satisfies_required_tools.py (120 x2).
2. Value re-derived after the runner fix: 600 -> 300. 600 was sized against a
   47 s run that no longer exists.
3. The cost paragraph named the WRONG CAP and inverted the consequence. The
   binding cap is the gate TASK's `timeout: 60m`, below the pipeline's
   `timeouts.tasks: 70m`, so a slow test can never reach the latter. Per that
   pipeline's own measured probe: task-level timeout -> Failed, finally RAN ->
   a posted RED; timeouts.tasks -> finally NEVER RAN -> the unclearable pending
   the old comment wrongly warned about.
4. The guard passed VACUOUSLY with no spawn call in the file β€” mutant M11
   (delegate _spawn's body to a testlib helper) SURVIVED. Now asserts >= 1
   bounded spawn inside _spawn, killed by its own message.
5. `_SPAWNING_CALLABLES` claimed "every subprocess entry point that can start a
   child" and was not. Added getoutput/getstatusoutput (the sharp case: no
   timeout parameter exists, so unbounded by construction) and replaced the
   claim with an enumeration naming what is deliberately NOT covered.
6. "all three merged" was wrong: #1450 is still OPEN.

Mutation battery, control green, each killed by the assertion that claims it:
  M13 getoutput          KILLED   M9  Popen().communicate()  KILLED
  M14 getstatusoutput    KILLED   M11 vacuous pass           KILLED (was SURVIVED)
  M2  import as sp       KILLED

87 passed across test_run_tests_targets + the five scoped_harness consumers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCmdWjiRn4PtLGTJrgmiX1
Claude-Session-Id: 460124dc-bb3d-4afb-b9e5-0687bec64e44
@ZacxDev

ZacxDev commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Round 1 β€” findings fixed, and the operator chose to fix the ROOT CAUSE in this PR

Round 1 was dispatched blind (diff + checklist, no prior conclusions). It returned three
🟑 and three 🟒, verdict "safe to merge". All six are fixed, and the scope changed: the
operator's call was to fix the cause rather than file it.

πŸ”΄ The root cause is now fixed β€” run-tests.sh

run-tests.sh drops the hook-test and shell-test families on a --files run, keyed on
SCOPED_MODE, which only --files sets. So a --targets run executed both families in
full, and no --targets value can name either. Re-keyed on SCOPE_STATE != FULL.

invocation before after
--targets scripts/collector/i3/tests 47 s (load ~52) 9 s (load ~46)

πŸ”΄ The skip is announced on the PARTIAL surface too. Widening the skip without widening
the announcement would be a run silently executing two families fewer than its own SUMMARY
claims β€” the #276 shape this runner exists to refuse. Both moved together.

⚠ It does not drag the rest of SCOPED_MODE along: a PARTIAL run still enforces every
per-target floor, GUARD 7's REQUIRED direction and GUARD 2's skip TOTAL, and still reports
SCOPE: PARTIAL. That is asserted, because reusing SCOPED_MODE wholesale would have passed
the skip assertions while silently suspending the floors.

Regression matrix: RED at origin/main's runner β€” and its failure output is the proof,
showing PASS scripts/claude-hooks/tests/test_claude_notify.py (script) inside a --targets
run β€” GREEN at HEAD. The pre-existing test_a_full_run_still_runs_the_hook_and_shell_families
is the positive control that FULL is unaffected.

Round-1 findings

🟑 1 β€” my cost paragraph named the wrong cap AND inverted the consequence. Verified
independently before adopting: the gate task's timeout: "60m" is below the pipeline's
timeouts.tasks: "70m", so a slow test can never reach the latter. And per that pipeline's
own measured three-way probe (Tekton v1.12.0): timeouts.tasks β†’ finally NEVER RAN β†’
posts nothing, checks pending forever; task-level timeout β†’ Failed, finally RAN β†’ a
posted red. So overrunning yields a legible red, not the unclearable pending I warned
about. Corrected, with the ~22.5m-queue caveat that makes the real post-queue budget ~37m.

🟑 2 β€” the guard passed VACUOUSLY. Both lists accumulate over calls the walk found, so a
file with no spawn call satisfies them by finding nothing. Mutant M11 β€” delegate _spawn's
body to a testlib/ helper, the natural shape of "move the bound somewhere shared" β€”
SURVIVED. Now asserts β‰₯1 bounded spawn inside _spawn; M11 is killed by that assertion's
own message. This is the same "passes hardest on the worst outcome" shape as the
bound-ABSENT check, one level up.

🟑 3 β€” _SPAWNING_CALLABLES' comment claimed "every subprocess entry point that can start a
child" and was not.
Added getoutput/getstatusoutput β€” the sharp case, since they take
no timeout parameter at all and are unbounded by construction. Replaced the claim with
an enumeration that names what is deliberately not covered (os.system, from subprocess import *, assignment aliasing) rather than implying closure.

🟒 6 β€” "one rule, one place" was file-local, and I had added a fourth copy.
scripts/testlib/scoped_harness.py:48 already carried run(args, timeout: int = 600, …),
used by five test files. The constant now lives there as RUNNER_TIMEOUT_S and this file
imports it. Still unconsolidated, and stated in the code rather than left implicit:
test_run_tests_preconditions.py (300) and test_devshell_satisfies_required_tools.py
(120 Γ—2).

🟒 5 β€” "all three merged" was wrong. #1450 is still OPEN (mergedAt: null);
#1458 and #1462 merged over the red. Corrected in the comment.

🟒 4 β€” the PR body. Its "fixed preflight" sentence is retracted; see my earlier comment
and the body, now updated.

The bound was re-derived, not kept

600 was sized against a 47 s run that no longer exists. With the cause fixed the same run
is 9 s, so the value is now 300 β€” ~33Γ— headroom, and a worst case of 4Γ—300 s = 20 min
against the 60m task cap instead of 40 min.

Mutation battery β€” control green, each killed by the assertion that claims it

mutant outcome
M13 subprocess.getoutput (no timeout param exists) KILLED
M14 subprocess.getstatusoutput KILLED
M2 import subprocess as sp KILLED
M9 Popen(...).communicate(), unbounded KILLED
M11 spawn delegated out of the file (vacuous pass) KILLED β€” was SURVIVED

87 passed across test_run_tests_targets.py and the five scoped_harness consumers.

⚠ Round 1's verified-not-findings are worth keeping: all six moved spawn sites are
byte-equivalent pre/post on argv/cwd/env/capture_output/text; TimeoutExpired.stdout
is genuinely bytes under text=True, so the .decode() is correct; and "four real nested
runs" is exact β€” instrumented, 39 spawns of which 35 are --check-* early exits at
0.00–0.09 s.

1. Finding 1 (the cost paragraph named timeouts.tasks 70m when the binding cap is the gate TASK's own 60m, and inverted the consequence): claimed corrected after independently verifying both halves against the live pipeline definitions and that pipeline's own measured three-way timeout probe; claimed to now state that a task-level overrun posts a legible RED, and to carry the ~22.5m queue caveat leaving ~37m real execution budget.
2. Finding 2 (the AST guard passed vacuously when the file contains no spawn call; mutant M11 measured SURVIVING): claimed addressed by asserting at least one bounded spawn inside `_spawn`, and claimed re-verified by M11 now being KILLED by that assertion's own distinct message rather than by a neighbouring one.
3. Finding 3 (`_SPAWNING_CALLABLES`' comment claimed every subprocess entry point and did not): claimed addressed by adding getoutput/getstatusoutput and by replacing the universal claim with an enumeration that names os.system, star-import and assignment aliasing as deliberately NOT covered.
4. Finding 6 (one-rule-one-place was file-local; scoped_harness.py already held a fourth copy of the same 600 default used by five files): claimed addressed by moving the constant into testlib/scoped_harness.py as RUNNER_TIMEOUT_S and importing it here; claimed the two remaining unconsolidated sites are named in the code rather than left implicit.
5. Finding 5 ("all three merged" false for #1450): claimed corrected after re-checking all three via gh; #1450 OPEN, #1458 and #1462 merged.
6. SCOPE CHANGE, operator-directed: the root cause is fixed in run-tests.sh (SCOPED_MODE -> SCOPE_STATE != FULL), the skip announced on the PARTIAL surface, and the bound re-derived 600 -> 300 against the post-fix 9s measurement. Claimed red/green matrix for the new test: red at origin/main's runner, green at HEAD, with the pre-existing full-run test as the positive control.

…s on the evidence of a sixth

Round-2 delta audit, one finding, and it is a defect the round-1 fix introduced.

Moving the constant into testlib/scoped_harness.py was right; changing its VALUE
in the same edit was not. `RUNNER_TIMEOUT_S` is the default for `run()`, which
test_scoped_runs.py, test_scoped_mapper.py, test_scoped_scope_marker.py,
test_scoped_gate_contract.py and test_scoped_ledgers.py use across ~47 call
sites. Setting it to 300 halved the bound for all five, justified entirely by a
9s measurement taken in the sixth.

Their runs are not 9s. MEASURED on the dev host: 28.9s at load ~57 for
test_the_node_runner_reports_FULL_on_a_REAL_run; the audit observed 70.5s for a
sibling under different load. At 300 that is ~4-10x headroom, against the >2.55x
contention inflation that caused this PR β€” i.e. the narrowing would have
re-created here the exact failure class the runner fix removes elsewhere: a red
on tekton/devrc-pytests naming a test the diff cannot reach.

So 600 stands, unchanged from what this module already had. What the PR removes
is the six open-coded `timeout=120` copies, not the bound's value. The rejected
300 and the measurements that rejected it are recorded beside the constant so
nobody re-derives it from the 9s figure.

The worst-case-budget argument that motivated 300 is kept as information and
corrected: 4x600s = 40 min requires all four nested runs to HANG β€” after the
runner fix each measures 2-9s β€” and a genuine hang is a defect you want
surfaced, not absorbed.

Also from round 2's non-findings:
- `bounded_in_spawn` counted spawn calls sited in `_spawn`, not ones checked to
  carry a bound, while its message said "NO bounded spawn". Renamed to
  `spawns_in_spawn` and the message narrowed to what the expression measures.
- "a LEGIBLE RED" overstated it: the posted status is `error` carrying
  `COULD NOT RUN`, which CLAUDE.md tells readers is a broken gate rather than a
  bad change. The substantive claim (posts something vs pending forever) stands.
- Cited line numbers corrected: test_devshell_satisfies_required_tools.py is
  110,438 not 105,436. Re-verified all three cited sites by grep.

Mutation battery re-run, control green, M11 now killed by the renamed
assertion's own message. 105 passed across test_run_tests_targets, the five
scoped_harness consumers and test_run_tests_preconditions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCmdWjiRn4PtLGTJrgmiX1
Claude-Session-Id: 460124dc-bb3d-4afb-b9e5-0687bec64e44
@ZacxDev

ZacxDev commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Round 2 (delta) β€” one finding, and it was a defect the round-1 fix introduced

Range 40abd9a4..409b1d30. All five round-1 findings confirmed actually fixed, both guard
fixes re-verified by mutation against their own messages. The scope change was audited as new
work and the runner fix came back correct: SCOPE_STATE at the two skip sites can only be
{FULL, PARTIAL, SCOPED} (the NONE sites exit 0 first, UNKNOWN is unreachable past
GUARD 11), the announcement fires on both surfaces, none of SCOPED_MODE's other suspensions
leaked, and CI is always FULL β€” gate.sh refuses DEVRC_TARGETS outright and
flake.nix:489 passes no narrowing β€” so CI's coverage of the families is untouched. The 48 s
β†’ 2 s improvement was reproduced first-hand on both legs.

🟑 The one finding: I re-sized a shared bound on one consumer's evidence

Moving the constant into testlib/scoped_harness.py was right. Changing its value in the
same edit was not.

RUNNER_TIMEOUT_S is the default for run(), used by five other test files across ~47 call
sites
. Setting it to 300 halved the bound for all of them β€” justified entirely by a 9 s
measurement taken in the sixth. Their runs are not 9 s:

test wall
test_the_node_runner_reports_FULL_on_a_REAL_run 28.9 s (load ~57, my re-measurement)
a sibling under different load 70.5 s (audit's measurement)

At 300 that is ~4–10Γ— headroom, against the >2.55Γ— contention inflation that caused this
PR in the first place. The narrowing would have re-created here the exact failure class the
runner fix removes elsewhere: a red on tekton/devrc-pytests naming a test the diff cannot
reach.

So 600 stands β€” unchanged from what the module already had. What this PR removes is the
six open-coded timeout=120 copies, not the bound's value. The rejected 300 and the
measurements that rejected it are recorded beside the constant, so nobody re-derives it from
the 9 s figure.

The worst-case-budget argument that motivated 300 is kept as information and corrected:
4Γ—600 s = 40 min requires all four nested runs to hang β€” after the runner fix each measures
2–9 s β€” and a genuine hang is a defect you want surfaced, not absorbed.

This is the fix-round prose failure the ladder rules name explicitly: wider on one axis,
narrower on another
, in an edit whose commit message described only the widening.

Round 2's non-findings, acted on anyway

  • bounded_in_spawn counted spawn calls sited in _spawn, not ones checked to carry a
    bound, while its message said "NO bounded spawn". Renamed spawns_in_spawn; message
    narrowed to what the expression measures. (No coverage hole β€” the unbounded assertion
    above it already ran.)
  • "a LEGIBLE RED" overstated it: the posted status is error carrying COULD NOT RUN, which
    CLAUDE.md tells readers is a broken gate rather than a bad change. Corrected; the
    substantive claim β€” posts something vs pending forever β€” stands.
  • Cited line numbers were wrong: test_devshell_satisfies_required_tools.py is 110, 438,
    not 105, 436. Re-verified all three unconsolidated sites by grep.

Mutation battery re-run, control green, M11 killed by the renamed assertion's own message.
105 passed across test_run_tests_targets.py, the five scoped_harness consumers, and
test_run_tests_preconditions.py.

⚠ Round 2 ran the dev-host tier only and says so; it is not a gate claim. Merged-tree sandbox
results for the previous tip are posted above β€” I will re-run both tiers on this tip.

1. Finding 1 (re-sizing RUNNER_TIMEOUT_S 600 -> 300 narrowed five other test files, ~47 call sites, on the evidence of a 9s measurement taken in the sixth; their runs measure 28.9s locally and 70.5s in the audit, leaving ~4-10x headroom against a measured >2.55x contention inflation): claimed addressed by reverting the value to 600 β€” the module's pre-existing default β€” so the PR changes WHERE the bound is written and not WHAT it is; claimed the rejected 300 and the measurements rejecting it are recorded beside the constant so it is not re-derived from the 9s figure; claimed the worst-case argument is kept as information and corrected to note 4x600s requires all four nested runs to HANG.
2. Non-finding (bounded_in_spawn's name and message claimed more than the expression measured): claimed renamed to spawns_in_spawn with the message narrowed to spawn calls sited in _spawn, and claimed re-verified by M11 still being KILLED by that assertion's own message.
3. Non-finding ("a LEGIBLE RED" overstates the posted status): claimed corrected to name the `error` / COULD NOT RUN status and what CLAUDE.md says a reader should conclude from it, keeping the posts-something-vs-pending-forever claim.
4. Non-finding (cited line numbers 105,436 wrong): claimed corrected to 110,438 and claimed all three cited unconsolidated sites re-verified by grep.

…emselves

Round-3 delta audit. Every finding is in prose THIS ladder's own fix commits
added β€” the recorded failure mode, and none of it is a code defect.

A. "What this PR removes is the six open-coded timeout=120 copies, NOT the
   bound's value" β€” false for the scope that matters. Those six sites go
   120 -> 600, a 5x widening, and that widening is the PR's headline fix. What is
   unchanged is the SHARED CONSTANT, for the five files already reading it. As
   written it would tell a maintainer no timeout was raised anywhere. Both
   copies of the sentence now name the scope.

B. The comment on `spawns_in_spawn` said the `unbounded` assertion "above …
   has already run". It is BELOW β€” line 355 against the comment at 337 and the
   assertion at 348. The comment's whole argument was "this needn't check
   boundedness, that is already proven", which would have justified deleting the
   check it leaned on. Corrected, and the two are now stated as independent: one
   refuses an EMPTY set, the other a BOUNDLESS call.

C. "an `error` carrying COULD NOT RUN" β€” wrong string. Traced the reporter's
   branch selection in devrc-ci-pipeline.yaml: the COULD NOT RUN arm is guarded
   by BUILD_STATUS = "Succeeded", which a timed-out task does not satisfy; a
   SIGKILLed step leaves no steprc and lands on `KILLED: <leg> β€” the gate pod
   died at or after step <phase> (preempted/evicted/OOM/timeout)`. The pipeline
   states it itself: "A task-level timeout: expiring while a step is EXECUTING
   also SIGKILLs it, so it reads as KILLED". The conclusion (error, not a code
   failure, posts something) survives; the citation did not.

D. A four-line HANG BOUND paragraph was duplicated verbatim inside the docstring
   whose thesis is "one rule, one place". Deduplicated.

E. The revert deleted the one sentence quantifying a cost of 600 while keeping
   every sentence quantifying a cost of 300 β€” sweeping the arguments against the
   rejected value harder than against the adopted one. 4x600s = 40 min against a
   ~37 min post-queue budget is now stated plainly as the strongest argument
   AGAINST 600, with why it is accepted anyway.

Also: "~47 call sites" re-counted by AST β€” 45, of which 44 take the default.

Verified rather than trusted: 600 is the module's pre-existing default at
b315cdd; the six 120s sites; the five importing files; 110/438 and
preconditions:68; the gate task's 60m vs timeouts.tasks 70m; the three-way probe
table; no stale 300 claims to be live anywhere.

Guard battery re-run, control green, all five mutants killed by their own
messages. 105 passed across the seven affected files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCmdWjiRn4PtLGTJrgmiX1
Claude-Session-Id: 460124dc-bb3d-4afb-b9e5-0687bec64e44
@ZacxDev

ZacxDev commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Round 3 (delta) β€” four findings, all of them sentences this ladder wrote about itself

Range 409b1d30..e5a3fb28. Zero code defects. The substantive change was confirmed
correct: 600 is genuinely the module's pre-existing default at the PR base, the six 120 s
copies are consolidated behind it, the guard is intact and the vacuous-pass mutant was
re-killed independently. Every finding is prose the previous two fix rounds added β€” the
ladder's recorded failure mode, exactly.

🟑 A β€” "not the bound's value" was false for the scope that matters. Both the constant's
docstring and the test file said this PR changes where the bound is written, not what it
is. True of the shared constant (600 β†’ 600 for five files); false of the six sites this PR
exists to change
, which go 120 β†’ 600 β€” a 5Γ— widening that is the PR's own headline. As
written it would tell a maintainer no timeout was raised anywhere. Both copies now name the
scope.

🟑 B β€” I asserted an execution order that is backwards. The comment on spawns_in_spawn
said the unbounded assertion "above … has already run". It is at line 355, below the
comment (337) and the assertion it explains (348). The argument was "this needn't check
boundedness, that's already proven" β€” which would have justified deleting the very check it
leaned on. Corrected; the two are now stated as independent (one refuses an empty set, the
other a boundless call).

🟑 C β€” wrong string, traced rather than assumed. I wrote "an error carrying
COULD NOT RUN". Following the reporter's branch selection in devrc-ci-pipeline.yaml: the
COULD NOT RUN arm is guarded by BUILD_STATUS = "Succeeded", which a timed-out task does not
satisfy; a SIGKILLed step leaves no steprc and lands on
KILLED: <leg> β€” the gate pod died at or after step <phase> (preempted/evicted/OOM/timeout).
The pipeline says so itself: "A task-level timeout: expiring while a step is EXECUTING also
SIGKILLs it, so it reads as KILLED."
The conclusion β€” error, not a code failure, posts
something rather than hanging pending β€” survives; the citation did not. I'd taken it from
CLAUDE.md's prose without tracing the branch.

🟑 D β€” a four-line paragraph duplicated verbatim inside the docstring whose thesis is "one
rule, one place". Deduplicated.

🟒 E β€” I swept the arguments against the value I rejected harder than against the one I
adopted.
The revert deleted the single sentence quantifying a cost of 600 while keeping
every sentence quantifying a cost of 300. 4Γ—600 s = 40 min against a ~37 min post-queue
budget is now stated plainly as the strongest argument against 600, with why it is accepted
anyway (it needs all four runs to hang; each measures 2–9 s after the runner fix; a genuine
hang is a defect you want surfaced).

Also re-counted by AST: "~47 call sites" is 45, of which 44 take the default.

Verified rather than trusted

600 as the base default (git show b315cdd3:…), the six 120 s sites, the five importing files,
110/438 and preconditions:68, the gate task's 60m vs timeouts.tasks: 70m, the
three-way probe table, and that no stale 300 claims to be live anywhere. Round 3 could not
verify the round-2 audit's 70.5 s observation; it independently measured 16.5 s at load ~48 for
the same test, same order and same direction, so the argument holds on either figure and the
docstring now carries both.

Gate

Merged-tree sandbox, previous tip: pytests PASS collected=22142 passed=22140 skipped=2 failed=0, SCOPE: FULL (28/28) β€” with 10 === script lines, i.e. a FULL run still
executes both families. Guard battery re-run after these fixes: control green, all five mutants
killed by their own messages. 105 passed across the seven affected files. I will re-run
both tiers on this tip.

1. Finding A (both copies of "this PR changes WHERE the bound is written, not WHAT it is" were false for the six consolidated sites, which rise 120 -> 600): claimed corrected in scoped_harness.py and test_run_tests_targets.py to name the scope each half is true of, and to say the widening at those six sites is deliberate and is the PR's headline fix.
2. Finding B (the comment claimed the `unbounded` assertion runs ABOVE and had already run; it is below at line 355): claimed corrected to state the true order and to describe the two assertions as independent β€” one refuses an EMPTY set, the other a BOUNDLESS call.
3. Finding C ("an error carrying COULD NOT RUN" names the wrong arm; that arm is guarded by BUILD_STATUS = Succeeded): claimed corrected to name the KILLED description, with the pipeline's own sentence quoted; claimed the surviving conclusion (error, not a code failure, posts something) is kept and separated from the retracted citation.
4. Finding D (a four-line HANG BOUND paragraph duplicated verbatim in the docstring whose thesis is one-rule-one-place): claimed deduplicated.
5. Finding E (the revert deleted the only sentence quantifying a cost of 600 while keeping every sentence quantifying a cost of 300): claimed addressed by restoring the 40 > 37 comparison as the strongest argument AGAINST 600, stated as such, with the reason it is accepted anyway.
6. "~47 call sites" claimed re-counted by AST to 45, of which 44 take the default.

…OPS HERE

Round-4 delta audit. Two findings, both prose, both defects the round-3 fix
introduced while fixing round 2's.

1. Round 3 replaced one false claim with its OPPOSITE. It had said the PR
   changes "WHERE the bound is written, not WHAT it is" (false for the six
   consolidated sites); the replacement called the 120 -> 600 rise "the PR's
   headline fix" / "the whole point of the change" β€” which oversells 600 as a
   measured value and is contradicted twenty lines below by "120 WAS NEVER THE
   REAL DEFECT EITHER", and by commit 409b1d3's own subject, "the root cause,
   not the bound". Both copies now say the rise is a CONSEQUENCE of the
   consolidation, accepted; the headline fix is the runner scoping; 600 remains
   an unpinned hang bound. The two dead drafts are recorded so a third is not
   derived.

2. Round 3's fix for finding E deleted the 70.5s observation β€” the single worst
   recorded, i.e. the one most hostile to 300 β€” while adding a smaller new one,
   so the stated headroom improved from ~4-10x to ~10-18x with no note that
   evidence had been removed. That is the selective-sweep mechanism finding E
   was about, performed inside the commit fixing E. The 70.5s is restored and
   LABELLED (second-hand, load unrecorded) rather than deleted, with the range
   stated honestly as ~4x to ~18x depending which observation you take.

πŸ”΄ THE LADDER STOPS HERE, ON THE ATTRIBUTION GATE, NOT ON A CLEAN ROUND.
Measured, not asserted: round 3's fix (beb227e) and this one both change
100% comment lines β€” every added/removed line matches `^[+-]\s*#`. Two
consecutive rounds whose fixes changed ZERO payload lines means the ladder has
left the PR: rounds 2, 3 and 4 each found only the previous round's own
sentences, while the executable payload has been untouched since round 1.

The code was settled four rounds ago: the runner fix is 14 non-comment lines in
run-tests.sh and 4 in scoped_harness.py, green in both sandbox tiers on the
merged tree, with the guard mutation-killable by two independent mutants each
dying on its own message.

Nothing is filed as follow-up β€” round 4's two findings are fixed here, and it
reported no others.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SCmdWjiRn4PtLGTJrgmiX1
Claude-Session-Id: 460124dc-bb3d-4afb-b9e5-0687bec64e44
@ZacxDev

ZacxDev commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Round 4 β€” two findings, both fixed, and πŸ”΄ THE LADDER STOPS HERE ON THE ATTRIBUTION GATE

Round 4 confirmed all five of round 3's fixes landed β€” and found that two of them had introduced
new prose defects of their own.

🟑 1 β€” I replaced one false claim with its OPPOSITE. Round 3 corrected "this PR changes
WHERE the bound is written, not WHAT it is" (false for the six consolidated sites). Its
replacement called the 120 β†’ 600 rise "the PR's headline fix" / "the whole point of the
change"
β€” which oversells 600 as a measured value, and is contradicted twenty lines below
by πŸ”΄ AND 120 WAS NEVER THE REAL DEFECT EITHER, and by commit 409b1d30's own subject: "the
root cause, not the bound"
. Both copies now say the rise is a consequence of the
consolidation, accepted; the headline fix is the runner scoping; 600 remains an unpinned hang
bound. Both dead drafts are recorded so a third is not derived.

🟑 2 β€” the fix for finding E used the very mechanism finding E was about. E was "you swept
the arguments against the value you rejected harder than against the one you adopted". The fix
for it deleted the 70.5 s observation β€” the single worst recorded, i.e. the one most hostile
to 300 β€” while adding a smaller new one, so the stated headroom improved from ~4–10Γ— to ~10–18Γ—
with no note that evidence had been removed. Restored and labelled (second-hand, load
unrecorded) rather than deleted, with the range stated honestly as ~4Γ— to ~18Γ— depending which
observation you take. Deleting a datum because you cannot verify it is exactly backwards; label
it.

Why the ladder ends here, and it is not a clean round

Measured, not asserted: round 3's fix (beb227e0) and round 4's (51c68310) both change
100% comment lines β€” every added and removed line matches ^[+-]\s*#. Two consecutive
rounds whose fixes changed zero payload lines is the attribution gate: the ladder has left
the PR.

That matches what the rounds found. Rounds 2, 3 and 4 each found only the previous round's own
sentences
, while the executable payload has been untouched since round 1:

round found payload changed by its fix
0 requirement challenge β†’ root cause fixed in this PR β€”
1 3 🟑 3 🟒 β€” real guard + prose defects non-zero
2 1 🟑 β€” a narrowing round 1's fix introduced non-zero (value revert)
3 4 🟑 1 🟒 β€” all prose rounds 1–2 wrote zero
4 2 🟑 β€” both prose round 3 wrote zero

The independent auditor reached the same conclusion unprompted: "stop the prose ladder rather
than run round 5 … a fifth round is more likely to write a new one than to find a real defect."

πŸ”΄ Stating what I am NOT doing, so this is distinguishable from convergence: this is the
attribution gate, not a clean round. Round 4 returned findings; they are fixed here. It reported
no others, so nothing is filed as follow-up. If a fifth round would find anything, it would be a
sentence in this commit β€” and that is precisely the cost the gate exists to stop paying.

What actually ships

The code settled four rounds ago: 14 non-comment lines in scripts/run-tests.sh (the runner
fix) and 4 in scripts/testlib/scoped_harness.py (the constant and its use in run()'s
signature). Everything else is tests and the prose above.

Verified on the merged tree, sandbox tier, one derivation at a time: pytests PASS
collected=22142 passed=22140 skipped=2 failed=0 SCOPE: FULL (28/28) with 10 === script
lines
β€” a FULL run still executes both families, which is the property the whole runner change
had to preserve. Guard green and killable by two independent mutants, each dying on its own
message. Re-running both tiers on this final tip.

@ZacxDev

ZacxDev commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Final merged-tree gate β€” both tiers green on the closing tip

origin/main 3d2a14bf + 51c68310, sandbox tier, derivations built ONE AT A TIME:

  • pytests PASS β€” collected=22142 passed=22140 skipped=2 failed=0,
    SCOPE: FULL (28 of 28 hermetic target(s)), RESULT: PASS (exit=0), NIXBUILD_RC=0
  • nodetests PASS β€” TOTAL suites=5 files=41 tests=1449 pass=1449 fail=0 (floor 1367),
    RESULT: PASS (exit=0), NIXBUILD_RC=0

πŸ”΄ And the property the whole runner change had to preserve: 10 === script lines in that
log β€” a FULL run still executes both the hook-test and shell-test families. Widening the skip
without preserving FULL would have silently stopped running two whole families in CI, which is
the failure this repo's own #276 incident is named for.

⚠ The dev-host tier (scripts/gate.sh) was not run on the merged tree. Both claims above are
about the sandbox tier at base 3d2a14bf.

@ZacxDev
ZacxDev merged commit c10709c into main Sep 11, 2026
3 checks passed
@ZacxDev
ZacxDev deleted the fix/run-tests-targets-subprocess-bound branch September 11, 2026 18:57
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