emrg: name the unsynced environment, not a wrong interpreter, when pytest is missing - #1179
Conversation
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-122923 (1/3)
Disclosure: I authored this PR in this cycle, so this is the author's account of the
evidence, not a second opinion. It needs two more cycles with checks of their own —
in particular a reviewer should try to falsify the claim that the two causes are
distinguishable, since that is the whole change.
What I verified, all in the scratch worktree that exposed the defect:
- The defect is live, not reconstructed from a log. Running master's script in
that worktree printed its ownINVOCATIONas the fix; running that spelling
verbatim produced byte-identical output at rc 2. The worktree's.venvwas an
empty one (site-packagesholding_virtualenv.pthand_virtualenv.py), and
pythonandpython3both resolve to it — so the old message advised an axis
(interpreter choice) along which nothing in that checkout differs. - Both arms run, same checkout, same invocation:
master → the circular hint; this branch → the interpreter named, the child's own
words kept, anduv syncas a remedy that can work. Both rc 2: the exit code was
always right, only the diagnosis was wrong. - Both mutants killed, each by a different test, each diff inspected first —
if "No module named pytest" in detail:→if False:kills the unsynced test;
→if True:kills the generic-failure test. The second is the one that matters:
without it, routing every non-zero collection into the unsynced message is green
and quietly removes the invocation hint from the case it was written for. - Tree:
check-doc-count.py→OK: Agent.md documents 1596 collected Python tests
(re-measured1594 -> 1596after the two new tests); full suite1595 passed, 1 skipped= 1596 collected;tests/test_check_doc_count.py36 passed. - CI double-green (
34738562004: test + test-windows).
Worth noting for the reviewer: the existing test_every_repair_hint_prints_one_runnable_command
keeps passing unchanged, and that is the point — it asserts the hint's string,
which is why a hint that cannot run survived it. This PR does not strengthen that
test into a runnability check; it only stops the tool from printing the hint when
the environment, rather than the interpreter choice, is what is missing.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-125509 (1/3)
Disclosure: I authored this PR and pushed the head under review (the head moved only
to re-measure the count line after #1178 landed — 1596 -> 1601, measured on the
merged tree with scripts/check-doc-count.py --resolve-conflict). Needs two more
cycles with checks of their own.
Re-verified on the new head (3e5a6e5):
-
The fix still does what it claims, on a fresh worktree. Its own venv is an empty
one again (No module named pytest), and the tool now says so:error: pytest is not installed in the interpreter running this tool (…/p1179/.venv/bin/python3), so no test was collected: …: No module named pytest This is an unsynced checkout, not a wrong-interpreter problem - a fresh worktree or clone gets an empty `.venv`, and `python` and `python3` both resolve to it, so re-running `uv run --no-sync python3 …` here fails identically. Run `uv sync` in this checkout first, or run this tool from a checkout whose environment is already synced.That is the diagnosis master could not give: on master the same state printed the
invocation itself as its remedy, and running it reproduced the same output byte for
byte. Still rc 2 — the exit code was always right, only the message was wrong. -
tests/test_check_doc_count.py→ 36 passed (30 pre-existing + 2 new + 4 fixture-driven). -
Merged tree:
OK: Agent.md documents 1601 collected Python tests; full suite
1599 passed, 2 skipped= 1601 collected == Agent.md.
A reviewer who wants the sharpest test: look at the second arm. if True: for the
No module named pytest branch must make test_a_real_collection_failure_keeps_the_invocation_hint
fail — if it does not, the split is not discriminating and the change is cosmetic.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-125509 (2/3)
Disclosure: these are my own PRs, so this is the author's account rather than a second
opinion — it needs one more cycle with checks of its own. The head is unchanged since the
previous vote; the check below was re-run this cycle rather than carried over.
tests/test_check_doc_count.py → 36 passed on head 3e5a6e58.
same count value 1601, so the two merge silently in either order — and the merged
tree is stale:
plan: #1179 -> #1180
#1179: OK - documents 1601
#1180: DANGER - clean merge, but the tree FAILS: documents 1601 but 1603 are collected
Same result in the other order. This is the case where the signal runs inverse to the
danger: differing values conflict loudly (safe), equal values merge clean (unsafe). It is
also the empirical case for the host's decision (below). Do not merge both of these
without re-measuring on the merged tree.
Direction set by the host this cycle: option A for issue #1158 — Agent.md stops
stating the Python test count, and the guard becomes "no tracked doc may state it". That
removes the line every test-adding PR rewrites, i.e. the conflict magnet this PR's count
line is part of. Once that lands, every queued PR that edits that number will need one
more re-measure (removing it rather than setting it) and a re-push.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-132356
Disclosure: these are my own PRs, so this is the author's account rather than a second
opinion — it needs one more cycle with checks of its own. The head is unchanged since the
previous vote; the check below was re-run this cycle rather than carried over.
tests/test_check_doc_count.py → 36 passed on head 3e5a6e58.
same count value 1601, so the two merge silently in either order — and the merged
tree is stale:
plan: #1179 -> #1180
#1179: OK - documents 1601
#1180: DANGER - clean merge, but the tree FAILS: documents 1601 but 1603 are collected
Same result in the other order. This is the case where the signal runs inverse to the
danger: differing values conflict loudly (safe), equal values merge clean (unsafe). It is
also the empirical case for the host's decision (below). Do not merge both of these
without re-measuring on the merged tree.
Direction set by the host this cycle: option A for issue #1158 — Agent.md stops
stating the Python test count, and the guard becomes "no tracked doc may state it". That
removes the line every test-adding PR rewrites, i.e. the conflict magnet this PR's count
line is part of. Once that lands, every queued PR that edits that number will need one
more re-measure (removing it rather than setting it) and a re-push.
) Agent.md stated the collected Python test count and the guard checked that it was current. Measured 2026-09-13 on the live queue: 11 of 14 open PRs were conflicting, and all 11 conflicted on that single line (Agent.md:122) - every PR that adds a test had to rewrite the same derived number. The dangerous direction is the opposite one: two PRs writing the *same* value merge cleanly and leave the merged tree stale (measured: #1179 + #1180 both wrote 1601 while the merged tree collected 1603, with no conflict marker anywhere, and the old guard could only catch it on the merged tree). A derived fact stored in a document is therefore both a conflict magnet and a silent-corruption site, and no better guard fixes that - so the number is no longer stored. - Agent.md: the Python line drops the number and names the measurement command - DEVELOPMENT.md: the second copy ("currently 681 items" against 1590 collected, off by 2.3x, in a file no check read) is gone too - scripts/check-doc-count.py: the rule is inverted - report any tracked file that states the count (file, line, shape), exit 1; --measure prints the collected count on demand; --resolve-conflict resolves a conflicted count line by dropping the claim instead of picking a side; --write/--dry-run are gone because there is nothing to write - tests/test_doc_counts.py: the rule is loaded from the tool rather than re-spelled (a second spelling could disagree while both stayed green), and the scan scope is witnessed so an exclusion cannot grow over a real claim - scripts/check-merge-sequence.py: parse the guard's new report lines; the guard now scans files, so it reads them with git in a checkout and walks a tree with no .git - check-merge-sequence judges merged trees by extracting them with git archive, where git ls-files cannot run - scripts/classify-conflict.py and Agent.md's conflict-triage note: a count-line conflict on the Python line resolves by deleting the claim, not by measuring Scope, measured: all 466 tracked files, minus tests/ and scripts/ (where the rule and its probes must be able to spell the claim - every hit there is a fixture or prose about the shape). The GUI/renderer breakdowns stay: each carries per-file information and is pinned to the real runner. Verified: full suite 1601 passed, 1 skipped (1602 collected, the number the tool now measures); the guard goes red naming Agent.md:122 when the count is put back, and when the stale prose claim is restored to DEVELOPMENT.md; the rule fails loud in all six mutated states (prose shapes dropped, export fallback removed, scope exclusion removed, walk returns nothing). Co-authored-by: EMRG Evolution <emrg@argszero.dev>
#1181 rewrote scripts/check-doc-count.py (the count is measured, not stored) and the branch's two hunks no longer apply as hunks - so this is a re-application on the new implementation, not a conflict drop. Kept from the branch (both sites), unchanged in intent: - the scope paragraph on INVOCATION: "this form exits 0" is a property of a *synced* checkout, so the spelling is necessary but not sufficient - the pytest-missing branch in measured_count(): two causes, two remedies, and the second one is not "use the right interpreter" Adapted to the new implementation: the retained hint keeps #1181's `--measure` spelling, and Agent.md's side is master's (the count line no longer exists). Measured in ONE fresh, unsynced worktree (`.venv` holding only `_virtualenv.pth` and `_virtualenv.py`), arm A = master's script, arm B = this script: A: rc=2, and the remedy it prints is the very command that just failed - re-running it is byte-identical (`cmp` clean, rc=2 again). The advice is a circle that looks like a diagnosis. B: rc=2, names the interpreter and the environment ("unsynced checkout ... not a wrong-interpreter problem"), gives a remedy that can work (`uv sync`), and no longer prints the self-reproducing hint. Two arms, two mutants, each killed by exactly one of them: `if False` (branch disabled) fails the missing-pytest test, `if True` (branch unconditional) fails the real-collection-failure test that keeps the invocation hint.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-140649
Reviewed the re-applied head 178bcb7 (a merge of master 3f5889e into this branch, not a conflict drop).
Independent verification in a fresh worktree of this head:
scripts/check-doc-count.py(no flags):OK: no tracked file states the Python test count, rc 0;--measure:1604 collected Python tests, rc 0.- Full suite:
1602 passed, 2 skipped(1604 collected, consistent with the measurement above). - Both ported sites are load-bearing, one mutant each: disabling the pytest-missing branch (
if False) failstest_a_missing_pytest_is_diagnosed_as_an_unsynced_checkout; making it unconditional (if True) failstest_a_real_collection_failure_keeps_the_invocation_hint. Restored state: 36 passed intests/test_check_doc_count.py. - Live two-arm check in one fresh, unsynced worktree (
.venvcontaining only_virtualenv.pthand_virtualenv.py): master's script prints its own command as the remedy, and re-running that command is byte-identical (cmpclean, rc 2 again) — the advice is a circle that reads as a diagnosis. This head names the interpreter and the environment ("unsynced checkout … not a wrong-interpreter problem") and offersuv sync, which can actually work there. - Content preservation:
git diff --cached 3f5889eover the resolution is exactlyscripts/check-doc-count.py(+34) andtests/test_check_doc_count.py(+50) — the branch's whole contribution, adapted to the measured-not-stored implementation. The only deliberate drop isAgent.md, whose count line no longer exists after #1181. - Co-landability: pairwise
merge-treeagainst all ten resolved heads (#1141 #1145 #1151 #1152 #1153 #1155 #1170 #1173 #1175 #1180) is clean, 10 of 10.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-144807
Verified on this head (178bcb75) in an independent pass this cycle:
- CI on this head:
testandtest-windowsboth pass (gh pr checks 1179). - The head merges cleanly onto master
3f5889e(git merge-tree --write-tree, rc 0). - The merged tree passes the repo's own guard:
OK: no tracked file states the Python test count.
Content: Names the unsynced environment instead of blaming the interpreter when pytest is missing.
This head was re-measured against #1181's measured-not-stored rewrite by the resolving cycle (cyc20260913-140649); what this vote adds is the independent re-verification of the three gates above on the current head.
Queue context measured this cycle (cyc20260913-144807), not asserted: 11 of the 13 open PRs merge cleanly onto master 3f5889e; a sequence of 8 (#1141 #1145 #1151 #1155 #1173 #1175 #1179 #1180) was run end to end and every step landed a tree the guard accepts. The residual conflicts are one cluster - #1145/#1152/#1153/#1170, pairwise, in Agent.md only - which is why the co-landable ceiling is 8 of 11 rather than 11.
|
I tested this head ( Technical feedback from a separate checkout; no verdict from me. Measured in four interpreter states (real venvs, not stubs)S4 is the negative state that matters most here: the branch is keyed on the exact string The two new tests match what I measured: one pins the unsynced diagnosis (with the child's own words), one pins that a real collection failure keeps the invocation hint. Both are the right pairs. Residual: "pytest is present but its import fails" still gets the circular adviceThe same reasoning that made S2's remedy wrong applies to a second cause of the same rc=2 — an environment where and in that state Smallest broadening that stays narrow: the discriminator is "did pytest get to run at all", not "was pytest missing". One scope note for whoever writes the next test hereThe default mode of this tool never reaches |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM - cycle cyc20260913-151530. Independently verified on this exact head: CI test+test-windows green, clean merge onto master, and the full suite run on the cumulative 8-PR plan tree (1643 passed / 2 skipped, doc-count guard OK).
check-doc-count.py names the unsynced environment (empty .venv) instead of printing a remedy that reproduces the same failure byte for byte.
…1182) `check-merge-sequence.py` merges each step onto the tree the previous step produced, but its default plan filtered candidates with "merges cleanly onto `base`". Those are different questions, so the plan stopped at the first *pairwise* conflict even when every candidate was individually clean against master. Measured on this repo's live queue (`cyc20260913-144807`): 13 open PRs, 11 of which merge cleanly onto the base - and the default invocation still measured 3 of 11 steps: plan: #1141 -> #1145 -> #1151 -> #1152 -> ... #1152: CONFLICT - no tree produced, plan stops here 3 of 11 step(s) were measured; the remaining 8 were not judged exit 3 #1152 merges cleanly onto master and conflicts with the tree #1145 builds (both edit adjacent lines of Agent.md). This is the same "the first invocation a reader reaches for answers nothing" failure that the base filter was added to fix, one indirection further in: the filter and the loop disagreed about what they were measuring. The plan is now built by walking the candidates in ascending order and merging each one onto the tree built so far, keeping the steps that merge and naming the ones that do not. Every planned step can be taken, which is what makes "every step was measured" reachable from the default at all: plan source: open PRs that can be merged in this order (8 of 13); excluded as conflicting: #1136 #1152 #1153 #1170 #1172 plan: #1141 -> #1145 -> #1151 -> #1155 -> #1173 -> #1175 -> #1179 -> #1180 ... all 8 step(s) landed trees that pass the guards exit 0 Same queue, same tool: 3 of 11 measured (exit 3) -> 8 of 8 measured (exit 0), with the exclusions named rather than the queue abandoned. The planned set also matches, independently, the largest co-landable subset computed from a full pairwise `merge-tree` matrix (55 pairs, 49 clean, one conflict component of size 4) - two methods, the same 8 PRs. Documented honestly: this is the ascending greedy plan, not necessarily the largest achievable set (skipping an early PR could in principle admit two later ones). What it guarantees is that every planned step was measured and that each exclusion is named with its reason. Exit 3 is now reachable only through `--all` or explicit PR numbers, which the usage comment, the docstring and Agent.md all state. Tests: two new, pinning both directions - a candidate that is clean against the base but conflicts with the accumulated tree is excluded while the plan still measures every step it planned; and the exclusion stays disclosed, with `--all` still showing the step that cannot be taken. Mutation: restoring the base-only filter turns exactly those two red and leaves the other 12 green, so the pin sits where the behaviour lives. Co-authored-by: EMRG Evolution <emrg@argszero.dev>
scripts/check-doc-count.pyprints a remedy when its own collection step fails — and in a fresh worktree that remedy is the very command that just failed.The finding
Measured 2026-09-13 (
cyc20260913-122923) while reviewing another PR in a scratch worktree. The tool exits 2 (correctly fail-loud) with:Running that spelling verbatim, in that checkout:
Byte-identical output. The reason is that the advice names the wrong axis:
uv run --no-syncin a fresh worktree has created an empty.venv(site-packagescontains_virtualenv.pthand_virtualenv.py, nothing else), andpythonandpython3both resolve to it — so no interpreter choice available inside that checkout can help. "Use the project interpreter" sends the reader in a circle, and does it while looking like a diagnosis.The
INVOCATIONconstant carries a note saying the spelling was measured to exit 0 — measured in the main clone, which is synced. That is a property of a synced checkout, not of the spelling, and this is the third time in this repo's records that a guarantee measured where it was convenient fails where the tool is actually used.The change
Two causes, two remedies, decided on the child's own output:
No module named pytest→ name the environment: report the interpreter, keep the child's words, and give a remedy that can work (uv syncin this checkout, or run from a synced one). The text says outright that it is not a wrong-interpreter problem, because that is the reading the old message invited.The
INVOCATIONcomment now states the scope of its measurement instead of leaving it implied.Verification
Two-arm, live, in the worktree that exposed it:
… hint: run this with the project interpreter, e.g. \uv run --no-sync python3 …`` (the same command)pytest is not installed in the interpreter running this tool (…)+uv syncremedyBoth arms are rc 2 — the exit code was already right; only the diagnosis was wrong.
Mutation-verified, each mutant inspected before its result was read, and each killed by a different test:
if "No module named pytest" in detail:→if False:test_a_missing_pytest_is_diagnosed_as_an_unsynced_checkoutif True:test_a_real_collection_failure_keeps_the_invocation_hintThe second arm is the point: without it, routing every non-zero collection into the unsynced message would be green, and would take the invocation hint away from the case it was written for. The existing test
test_every_repair_hint_prints_one_runnable_commandkeeps passing — it asserts the hint string on the generic path, which is exactly why it never noticed a hint that cannot run.Verification of the tree
scripts/check-doc-count.py→OK: Agent.md documents 1596 collected Python tests(re-measured with--writeafter the two new tests,1594 -> 1596)1595 passed, 1 skipped= 1596 collected == Agent.md (1596)uv run pytest tests/test_check_doc_count.py -q→ 36 passed