Skip to content

fix(hygiene): untrack .coverage — the verdict command was rewriting a tracked binary - #119

Merged
stranske merged 1 commit into
mainfrom
claude/peaceful-sanderson-a5719c
Aug 25, 2026
Merged

fix(hygiene): untrack .coverage — the verdict command was rewriting a tracked binary#119
stranske merged 1 commit into
mainfrom
claude/peaceful-sanderson-a5719c

Conversation

@stranske

Copy link
Copy Markdown
Owner

What

git rm --cached .coverage, root-anchored /.coverage + /.coverage.* in .gitignore, and five cases in tests/test_repo_artifact_hygiene.py so a recurrence is caught.

Why

.coverage — a 90 KB coverage.py SQLite database — has been TRACKED on main since #109, a typing PR whose every other changed file is about mypy. It was in the working tree when the branch was staged, the same accident that put src/UNKNOWN.egg-info/ on #113.

It is build output, and the churn is mechanical rather than occasional: verify.py's coverage_reset() UNLINKS ROOT/.coverage and ROOT/.coverage.* before every instrumented run, then coverage_combine_and_report() writes them again. So while tracked, this repo's own verdict command showed up as a deletion of a tracked binary followed by a re-add of different bytes — and an uncommitted copy blocked a branch switch.

tests/test_repo_artifact_hygiene.py exists precisely to assert that generated artifacts are gitignored and untracked, and grep -n coverage on it returned nothing. The suite had no opinion about the one artifact this repo produces itself.

The patterns, and why each detail

  • BOTH, because two different steps write them: --parallel-mode writes one .coverage.<host>.<pid>.<random> per instrumented child (~90 subprocesses per run) and coverage combine merges them into .coverage. Ignoring only the combined file leaves the per-child data files unignored.
  • ROOT-ANCHORED, per the rule fix(hygiene): untrack the per-run langsmith worker-attempt artifact #72 established for every debris pattern in this file — and true of the producer here: verify.py globs and unlinks ROOT, so the checkout root is the only place these land.
  • /.coverage.* and NOT /.coverage* — the second also swallows .coveragerc, coverage.py's own config file, which is source.
  • Untracking is what makes the patterns bite. git add -A skips an ignored UNTRACKED path but stages an ignored TRACKED one, so the pattern alone would have changed nothing.
  • Placed above the template-managed block, which a Template-Version bump rewrites.

Deliberate break → revert, all three directions

break failed
too narrow — drop /.coverage.* test_coverage_data_files_are_ignored[.coverage.a-host.12345.678901]
too broad by kind — /.coverage* test_coverage_sources_are_not_swallowed[.coveragerc]
ignored but re-TRACKED — git add -f .coverage test_no_coverage_data_file_is_tracked and test_coverage_data_files_are_ignored[.coverage] (check-ignore is index-aware)

Each broke exactly one case and nothing else; the revert was byte-identical with all 19 green. Recorded in the test file.

The DEPTH direction the langsmith patterns record has no in-tree near-miss here and is not claimed to — nothing tracked in this repo has a basename beginning .coverage. The anchoring is kept for the producer's reason instead.

Floor

453 → 458. Exactly five new tests, all in test_repo_artifact_hygiene.py. No ceiling moved and nothing new is skipped — all five ask GIT about a path in this checkout (check-ignore / ls-files), so none needs a populated ledger, an agent CLI or ~/.codex, and all five run on a bare runner.

Measured on the merge result: fast-forwarded onto origin/main 5c769e0 (recorded floor 453) and re-fetched to confirm main had not moved again before measuring.

Verified

pytest:     458 passed, 0 failed, 0/26 max skipped (458 collected; floor 458)
selftests:  85 of 85 modules ran, 0/7 max skipped
5 of 5 gates green

black --line-length 100 clean on the one Python file touched; ruff check clean.

🤖 Generated with Claude Code

… tracked binary

`.coverage`, a 90 KB coverage.py SQLite database, has been TRACKED on main since #109 —
a typing PR whose every other changed file is about mypy. Nobody chose to commit it; it
was in the working tree when the branch was staged, the same accident that put
src/UNKNOWN.egg-info/ on #113.

It is build output, and the churn is mechanical rather than occasional: verify.py's
coverage_reset() UNLINKS ROOT/.coverage and ROOT/.coverage.* before every instrumented
run and coverage_combine_and_report() writes them again. So while tracked, the repo's own
verdict command showed up as a deletion of a tracked binary followed by a re-add of
different bytes, and an uncommitted copy blocked a branch switch.

BOTH patterns, because two different steps write them: `--parallel-mode` writes one
`.coverage.<host>.<pid>.<random>` per instrumented child (~90 subprocesses per run) and
`coverage combine` merges them into `.coverage`. Ignoring only the combined file leaves
the per-child data files unignored.

ROOT-ANCHORED per the rule #72 established for every debris pattern in .gitignore, and
true of the producer here: verify.py globs and unlinks ROOT, so the checkout root is the
only place these land. `/.coverage.*` and NOT `/.coverage*` — the second also swallows
`.coveragerc`, coverage.py's own config file, which is source.

And UNTRACKING is what makes the patterns bite: `git add -A` skips an ignored UNTRACKED
path but stages an ignored TRACKED one, so the pattern alone would have changed nothing.

test_repo_artifact_hygiene.py had NO case for this file — `grep -n coverage` on it
returned nothing — so nothing would have caught a recurrence. Five added, matching the
suite's three halves: two parametrized ignore cases, one untracked case, and two
must-stay-committable cases. Break -> revert performed in all three directions the
patterns can fail (too narrow, too broad by kind, ignored-but-re-tracked); each broke
exactly one case, and the revert was byte-identical. Recorded in the file.

The depth direction the langsmith patterns record has no in-tree near-miss here and is
not claimed to; the anchoring is kept for the producer's reason instead.

Verified: 458 passed, 0 failed, 0 skipped, 85/85 selftests, 5/5 gates. Floor 453 -> 458,
measured on the merge result after fast-forwarding onto origin/main 5c769e0 and
re-fetching to confirm main had not moved again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 48 minutes.

View limit details

Limit details: You’ve used the included review currently available. Your 70 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 80434493-f16a-403e-a73e-f44e7e94f170

📥 Commits

Reviewing files that changed from the base of the PR and between 5c769e0 and 61ec4ee.

📒 Files selected for processing (4)
  • .coverage
  • .gitignore
  • .verify-floor.json
  • tests/test_repo_artifact_hygiene.py

Comment @coderabbitai help to get the list of available commands.

@agents-workflows-bot

Copy link
Copy Markdown
Contributor

Workflow source needed

PR #119 needs either a linked GitHub issue or one valid non-issue Workflow Source before PR metadata automation can manage it safely.

Please do one of:

  • Add <!-- meta:issue:123 --> or a normal Closes #123 / Related to #123 line.
  • Check one Workflow Source option in the PR body.
  • Add a hidden marker such as <!-- workflow-source:local_request -->, <!-- workflow-source:manual_remote -->, <!-- workflow-source:review_followup -->, <!-- workflow-source:sync_campaign -->, or <!-- workflow-source:dependabot -->.
  • Add a workflow source label such as workflow:source-direct-pr, workflow:source-local-request, workflow:source-review-followup, workflow:source-sync, or workflow:no-automation.

Once a valid source is present, this warning will not be reposted.

@stranske-keepalive

Copy link
Copy Markdown

Automated Status Summary

Head SHA: 27ce7a1
Latest Runs: ⏳ pending — Gate
Required: core tests (3.12): ⏳ pending, core tests (3.13): ⏳ pending, docker smoke: ⏳ pending, gate: ⏳ pending

Workflow / Job Result Logs
(no jobs reported) ⏳ pending

Coverage Overview

  • Coverage history entries: 1

Coverage Trend

Metric Value
Current 34.25%
Baseline 0.00%
Delta +34.25%
Minimum 70.00%
Status ❌ Below minimum

Top Coverage Hotspots (lowest coverage)

File Coverage Missing
src/capability_effectiveness.py 0.0% 154
src/capability_firing_monitor.py 0.0% 192
src/capability_matcher_proposals.py 0.0% 111
src/capability_opportunity.py 0.0% 143
src/capability_propensity.py 0.0% 1669
src/ccusage_reconcile.py 0.0% 286
src/codemod_lane.py 0.0% 351
src/evidence_acquisition.py 0.0% 103
src/exploration_collection.py 0.0% 331
src/feature_scan.py 0.0% 118
src/frontend_verify.py 0.0% 255
src/improvement_log.py 0.0% 248
src/issue_readiness.py 0.0% 507
src/keepalive_evidence.py 0.0% 378
src/keepalive_supervisor.py 0.0% 322

Low Coverage Files (<50.0%)

File Coverage Missing
src/capability_effectiveness.py 0.0% 154
src/capability_firing_monitor.py 0.0% 192
src/capability_matcher_proposals.py 0.0% 111
src/capability_opportunity.py 0.0% 143
src/capability_propensity.py 0.0% 1669
src/ccusage_reconcile.py 0.0% 286
src/codemod_lane.py 0.0% 351
src/evidence_acquisition.py 0.0% 103
src/exploration_collection.py 0.0% 331
src/feature_scan.py 0.0% 118
src/frontend_verify.py 0.0% 255
src/improvement_log.py 0.0% 248
src/issue_readiness.py 0.0% 507
src/keepalive_evidence.py 0.0% 378
src/keepalive_supervisor.py 0.0% 322

Updated automatically; will refresh on subsequent CI/Docker completions.


Keepalive checklist

Scope

No scope information available

Tasks

  • No tasks defined

Acceptance criteria

  • No acceptance criteria defined

@stranske
stranske merged commit 5b4b526 into main Aug 25, 2026
35 checks passed
@stranske
stranske deleted the claude/peaceful-sanderson-a5719c branch August 25, 2026 00:54
@github-actions

Copy link
Copy Markdown
Contributor

Workflow state fingerprint for Agents Gate Followups. Do not edit.

stranske added a commit that referenced this pull request Aug 30, 2026
…ported as 34.11% (#159)

* ci: measure this repo's REAL coverage, which is 82.1% and has been reported as 34.11%

pr-00-gate.yml measures coverage with pytest alone. Measured 2026-08-29 by running the command
this job adds: 82.1%, over 44,740 statements with 8,026 missed, combined from 94 instrumented
processes. The Gate reports 34.11%. Forty-eight points.

WHY. 79 of 102 modules are exercised by a `--selftest` entry point rather than a pytest file --
the original design, from the initial commit -- and a selftest runs as a SUBPROCESS that
pytest-cov cannot see. So the Gate has been measuring the instrument's blind spot and calling it a
score: the same defect stranske/Workflows#3251 drained out of the fleet reporter, where a number
nobody could measure rendered as a number that was measured.

It mattered beyond cosmetics. Every other repo in the fleet now carries a
config/coverage-baseline.json set at measured current; this one was deliberately SKIPPED, because
a baseline against the wrong instrument would bake the blind spot in permanently.

A SEPARATE JOB, NOT A FLAG ON THE VERDICT:
  * `--coverage` wraps ~94 child processes in `coverage run --parallel-mode`, so it is materially
    slower than the verdict, which is on the critical path for every PR. Measured in CI, the
    verify job is ~1m30s; this runs beside it rather than in front of it. (My first instinct was
    that this was too expensive to run at all -- from timing it locally at 7 minutes. That was the
    Dropbox tax CLAUDE.md warns about, not the real cost. CI is the number that decides.)
  * `--coverage` deliberately never touches the exit code, and verify.py pins that in a test,
    precisely so measuring cannot change a verdict. Its own job keeps that true by construction.

continue-on-error, because a coverage MEASUREMENT must never redden a green verdict -- but the
failure stays visible in the job list rather than being swallowed. Both summary branches
self-describe: verify.py emits `coverage: NO DATA` and `coverage: FAILED` as well as the combined
report, so an absent measurement prints as absent rather than as a low number.

NOT YET WIRED TO THE GUARD, and the summary says so rather than implying enforcement. Maint
Coverage Guard reads pr-00-gate.yml by a hardcoded workflowId and cannot see this job, so this
repo still has no baseline. Publishing the honest figure is step one; teaching the guard to read
it is a separate change -- the same hardcoded-workflowId limitation that makes
stranske/Counter_Risk invisible to it.

Verified: verify.py --coverage exits 0 and produces the report above. `.coverage` remains
gitignored (#119), so uploading it as an artifact cannot reintroduce the tracked binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ci: cite the CI figure, not the local one — they differ by 1.8 points and CI is authoritative

The first version of this job's comment quoted 82.1%, measured on the owner's machine. This
runner reports 80.3%: 44,741 statements, 8,828 missed, same 94 instrumented processes.

The difference is real, not noise. 802 statements execute only where this instance's
prerequisites exist -- agent CLIs, ~/.codex, a populated ledger -- and are missed on a bare
runner. They are exactly the paths env_prereq guards, so the gap is the skip surface showing up
in coverage rather than a measurement artefact.

CLAUDE.md already says to take coverage from CI rather than a local run. Quoting the local number
in the job that exists to establish the honest one would have overstated the repo by nearly two
points, in a comment future readers would treat as the record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): address CodeRabbit review on combined-coverage job

Pin checkout/setup-python/upload-artifact to verified SHAs, use shell: bash
on the coverage pipeline so verify.py exit status survives tee, and publish
the TOTAL row in the step summary.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Tim Stranske <tim@stranskemo.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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