Skip to content

v1.108.305 — Only the reader was never fixed

Choose a tag to compare

@jgravelle jgravelle released this 28 Aug 18:54
· 42 commits to main since this release

Fixed - the release checklist's CI-environment reproduce never built it

Step 2c read uv run --python 3.13 python -m pytest tests/ -q. CI runs
uv sync --locked --group dev --extra watch first. The documented command
synced nothing and named no extra, so it inherited whatever .venv happened
to hold
-- it looked correct for exactly as long as a previous sync's packages
survived.

⚠⚠ Caught mid-release, and the near-miss is the point: it returned EXIT 0 and
the totals reconciled EXACTLY
(8,740 + 18 new tests = 8,758), which are the
two things "green" means here. Meanwhile passed fell 8,721 -> 8,634 and
skipped rose 19 -> 124: 105 tests silently did not execute. Confirmed at
the source -- syncing with CI's flags printed + watchfiles==1.1.1, the
[watch] extra CI installs BY NAME.

Read the SKIP count, not just the exit code and the total. The documented
range is 19-26; a jump means the environment, not the code.

⚠⚠ The checklist lives in .claude/skills/release/SKILL.md, which is
gitignored (the v0.2.6 credential-leak fix), so a correction there is
machine-local and gone on a fresh checkout. Un-ignoring .claude/ to make it
testable would reintroduce the vector that got five releases yanked
, so the
durable copy now lives in CLAUDE.md and tests/test_ci_env_reproduce_command.py
binds it to .github/workflows/test.yml -- the two cannot drift apart unnoticed.

⚠ Third instance of one family, and CONTRIBUTING.md already carried the
sentence: CI installs with uv sync and never runs the command the docs give a
human.
First was pip install -e ".[test]" (an extra no repo declares); second
was -n 4 --dist loadfile under a bare python -m pytest, which collects
nothing and exits 0.

Added - a lock wait reports itself, because waiting looks like working (#557)

@Ticki84 ran the new phase breakdown on the first build that had it and it
answered immediately: save=9.906s of a 10.000s total, everything else
summing to 0.094s. The cost is entirely store.incremental_save.

⚠⚠ incremental_save takes an indexwrite process lock before it writes, so
from the caller's timer a CONTENDED LOCK and a SLOW WRITE are indistinguishable
-- both are just time spent inside save.
Only the wait itself can separate
them, and only process_locks can see it.

_Held.__enter__ now records waited_seconds and logs it: DEBUG for any wait,
WARNING past one second, with the holder NAMED (pid, client_id, age).
A multi-second stall on a single-file reindex is a user-visible problem rather
than a debug detail, and "something else holds the lock" without saying what
sends the reader hunting in the wrong process. watch-all watches every indexed
repo, so a second watcher or an editor-side MCP server is exactly the shape that
would queue here.

⚠ The round 10.000s is what makes contention the leading hypothesis -- real
work rarely lands on a round number -- but it is a hypothesis, and shipping the
instrument is cheaper than asking the reporter to test it.
Three earlier
hypotheses on this issue were each measured dead by the reporter.

Fixed - the machine's timezone chose the input format, so 3.10 broke in CI only

The shallow-boundary probe read git log --format=%aI. git renders a UTC
offset as Z, and datetime.fromisoformat could not parse Z until 3.11
, so
on 3.10 every boundary date came back unparseable.

⚠⚠ It could not be reproduced on the developer box, and the reason is the
lesson.
git emits Z only where the offset IS zero. Every CI runner is UTC;
this box is CDT and got -05:00, which 3.10 parses fine. The host's timezone
selected which spelling git produced
, so the integration tests were green
locally on all versions and red on 3.10 across both operating systems.
uv run --python 3.13 could not have caught it either -- it was never a version
the local clock could break.

test_parse_iso_accepts_both_offset_spellings pins all four spellings as a
UNIT, with no repository, no clock and no timezone. An integration test is
structurally incapable of guarding this: it can only observe whichever spelling
its host happens to produce. Two of the four cases fail against the old parser
under 3.10 on the non-vacuity pass.

The tri-state held under the fault and that is worth recording. An
unparseable boundary reported complete: None -- could not establish -- rather
than a confident coverage answer off a date nobody read. The design degraded
instead of lying, which is what the CI failure looked like: assert None is False, not a wrong verdict.

uv run ruff check src/ passed against the broken parser. Lint is not a
correctness signal, and a green lint on a hand-edited revert is not a restore.

Fixed - a scratch file shipped inside the published 1.108.304 sdist

relnotes.md -- a temporary copy of the CHANGELOG entry, written for
gh release create --notes-file -- was swept up by a git add -A in the
release commit. It is in the v1.108.304 tag and inside the sdist on PyPI.
Harmless content, permanently there: PyPI cannot be re-uploaded.

⚠⚠ The canary tests could not have caught it and never could.
tests/test_sdist_exclusions.py plants a canary under each NAMED excluded path
and proves it is absent -- it answers "did a known-bad path get in". A scratch
file has no name to plant a canary under. A denylist catches the instance; an
allowlist catches the class.
ALLOWED_ROOT_FILES now enumerates every file
permitted at the sdist root, so anything nobody decided on fails the build.

⚠ Both directions are asserted, because a list that drifts from the artifact
stops being a guard: test_no_unexpected_file_at_the_sdist_root catches an
addition, test_the_allowlist_is_not_stale catches an entry naming a file that
no longer ships. Both fail against their own defect on the non-vacuity pass, the
first naming relnotes.md exactly.

Build release notes OUTSIDE the repository. A .gitignore entry would
also work and is strictly weaker -- it protects only the spelling someone
remembered.

⚠⚠ The guard found a SECOND live instance within minutes of being written,
and it was the release engineer's own.
suite.log -- the file every gate run
in that session redirected pytest into, in the repository root -- failed the new
assertion on the first full-suite run. It had been there all day. A release
cut while one existed would have shipped it, and unlike relnotes.md a pytest
log carries absolute paths and usernames.
*.log is now gitignored (hatchling
honours the root .gitignore), but that is defense in depth: it protects one
spelling, and the allowlist is what catches the class.

Fixed - nine tools read a git window none of them could tell was truncated

git log --since=<N> days on a shallow clone returns a short log and exit
status 0. Nothing outside the observatory's own cloner detected that, so
get_churn_rate, get_hotspots, get_file_risk, get_delivery_metrics,
get_tectonic_map, winnow_symbols, decision_context, find_unused_paths
and health_radar.churn_surface all read a truncated history as a calm one.

⚠⚠ Fixed twice before, never in a READER. Practice 6 records
git fetch --depth=1 shortening an already-complete clone in the health-radar
Action; tests/test_observatory_clone_depth.py records the same defect in the
observatory's cloner, measured at 81.3 (B) shallow versus 75.6 (C) full at one
identical commit
, churn_surface the only axis that moved. Both fixes made
OUR clones deep. actions/checkout defaults to fetch-depth: 1, so every
user running the Action or jcodemunch-mcp health in their own CI still got a
flattering grade on their own pull requests.
Third instance of "we fix the
reported call site and leave the mechanism".

tools/_git_history.py asks coverage, not shallowness.
--is-shallow-repository is the mechanism; "the history reaches past the
window" is the property. Verified on real clones: --depth=900 at a 90-day
window is shallow AND complete (shallow_but_covers_window); the same clone at
365 days is not. A false alarm on a deep-but-bounded clone would teach people to
ignore the flag. A three-week-old repository is YOUNG, not truncated.

⚠ Tri-state. No git, no repo, an unreadable boundary: complete: None, never
False. churn_is_measurable() collapses None to "do not publish" at the one
place a caller must decide whether to issue a grade -- the _stop_rule rule,
where every uncertainty resolves to False.

⚠ Disclosure is silent on a complete history by design; a block on every
response is one nobody reads. An UNKNOWN is disclosed, because it is not a clean
bill of health.

Fixed - a grade was withheld the wrong way first, and the number got worse

⚠⚠ Recorded because the first fix was wrong in the flattering direction, which
is the direction that matters here.
The obvious gate was to pass
top_hotspot_score=None and let churn_surface be omitted, reusing the
convention runtime_coverage already uses. Measured on one tree: pre-fix
84.0 B, "fixed" 88.8 B, full-clone truth 77.3 C. Dropping a
low-scoring axis RAISES a mean
, so the fix moved the published grade further
from reality than the defect had.

⚠⚠ The error was collapsing two states this project separates everywhere else.
NOT APPLICABLE -- no trace was ever ingested, the axis does not apply, and
omitting it keeps the composite comparable -- is not COULD NOT MEASURE. Only
the first may be dropped silently.

compute_radar now takes unmeasurable_axes; when non-empty, composite and
grade are withheld entirely
(None) with grade_withheld and the measured
axes still reported, plus partial_composite for a caller who knowingly wants a
figure missing an axis. ⚠ The default path is byte-for-byte unchanged and a test
asserts it for both None and [].

⚠⚠ Two None sites the tests found, both user-facing, and one of them is why
.get(k, default) is not a guard
: diff_radar read
.get("composite", 0.0), and the default never fires when the key is present
with value None
-- it raised. Defaulting to 0.0 would have been worse: a
~77-point "regression" against a side that was never measured. And _verdict,
the one-line string printed on a contributor's pull request, would have rendered
a withheld composite as "no meaningful change" -- the reassuring answer, on
the single occasion nothing was measured.