Skip to content

fix(typing): drain the <=5-finding tail (exempt list 43 -> 26, 73 of 99 checked) - #95

Merged
stranske merged 1 commit into
mainfrom
claude/mypy-drain-batch-2
Aug 24, 2026
Merged

fix(typing): drain the <=5-finding tail (exempt list 43 -> 26, 73 of 99 checked)#95
stranske merged 1 commit into
mainfrom
claude/mypy-drain-batch-2

Conversation

@stranske

Copy link
Copy Markdown
Owner

Batch two. The cheap tail is now gone — future batches take one big module at a time.

before after
exempt modules 43 26
findings 430 374
modules checked 56 of 99 73 of 99

Three of my own fixes were wrong, and the suite caught each

Recording these because the pattern matters more than the diffs:

  1. A type fix that changed behaviour. I wrapped a bad-input probe as domain_target(str(bad)). That loop deliberately passes None to prove it's rejected — and str(None) is the perfectly valid slug "None", so the rejection never fired and the selftest failed on "blank slug accepted: None". Reverted to a narrow # type: ignore[arg-type] with the reason, which is what a deliberately-wrong-typed probe actually needs.
  2. dict(x) / list(x) don't satisfy mypy for an object — and they'd add a real copy at runtime. Replaced with cast(...), which states the shape the surrounding code already validated and compiles to nothing.
  3. Per-file mypy src/X.py is not the project run. Eleven modules I'd confirmed clean per-file still carried cross-module findings — which is why this batch cleared 17 modules rather than the 21 the tail projected. Measure with the project command.

Where an ignore is the right answer, it's narrow and says why

agent_auth_check monkeypatches Path.read_text inside its selftest to force the OSError path. That is exactly what method-assign exists to flag, and exactly where it's wrong. Three of those, each annotated.

No error code is disabled anywhere, and test_mypy_config_silences_nothing_by_error_code still forbids that.

Repeated patterns, fixed at the source

  • env: dict | None where the argument is os.environ — that's a Mapping[str, str], not a dict. Four modules (tick ×2 sites, capability_opportunity, exploration_backfill, after evidence_acquisition in batch 1).
  • claims.holder() returns None by design, and three selftests indexed straight through it. Binding it and asserting the holder exists is a stronger check than the one-liner it replaced.
  • Heterogeneous dict literals whose first entry pins a narrow value type, then conflict later — annotated once at the declaration (decision, out, checks ×2, bucket, base_kwargs, candidates).
  • Two lambdas with loop-captured default args, which mypy can't infer. The capture is load-bearing — it binds this iteration's values — so it survives in the replacement functions' signatures.

One config change, and its cost paid rather than dodged

mypy_path gained tests, so mypy can resolve the recurrence-fixture roster that capability_admission legitimately imports (declared via paths.TESTS_DIR). The check target stays src — but mypy then follows into that one test file, and its two findings were fixed, not excluded.

tick.py also lost a dead _unused = ([], [], ...) tuple my first rewrite left behind.

Test gate

python3 src/verify.py442 passed, 0 failed, 0 skipped, 85/85 selftests, 43/43 can-fire, 5/5 gates. ruff check . and black --check --line-length 100 clean; every touched module's selftest run.

mypy_exempt_max lowered 43 → 26.

What's left

374 findings across 26 modules, and it's now concentrated: capability_advisor (66), dispatcher (65), capability_propensity (51), runtime_ac_gate (34), capabilities (32) hold ~248 of them. Those are five per-module campaigns; skimming is finished.

🤖 Generated with Claude Code

… 73 of 99 checked

Batch two. The cheap tail is now gone, so future batches take ONE big module at a time.

  exempt modules 43 -> 26   |   findings 430 -> 374   |   checked 56 -> 73 of 99

THREE OF MY OWN FIXES WERE WRONG, and the suite caught each. Recording them because the pattern
matters more than the diffs:

  1. `research_subjects` — I wrapped a bad-input probe as `domain_target(str(bad))`. That loop
     deliberately passes None to prove it is REJECTED, and `str(None)` is the perfectly valid slug
     "None", so the rejection never fired and the selftest failed on "blank slug accepted: None".
     A type fix that changed behaviour. Reverted to a narrow `# type: ignore[arg-type]` WITH the
     reason — which is what a deliberately-wrong-typed probe actually needs.
  2. `dict(x)` / `list(x)` do not satisfy mypy for an `object`, and would add a real copy at
     runtime. Replaced with `cast(...)`, which states the shape the surrounding code has already
     validated and compiles to nothing.
  3. Per-file `mypy src/X.py` is NOT the project run. Eleven modules I had confirmed clean per-file
     still carried cross-module findings, which is why this batch cleared 17 modules rather than the
     21 the tail projected. Measure with the project command.

WHERE AN IGNORE IS THE RIGHT ANSWER, it is narrow and it says why: `agent_auth_check` monkeypatches
`Path.read_text` inside its selftest to force the OSError path, which is exactly what `method-assign`
exists to flag and exactly where it is wrong. Three of those, each annotated. No error code is
disabled anywhere, and `test_mypy_config_silences_nothing_by_error_code` still forbids that.

REPEATED PATTERNS, fixed at the source rather than the symptom:
  * `env: dict | None` where the argument is `os.environ` — that is a `Mapping[str, str]`, not a
    dict. Four modules (`tick` x2 sites, `capability_opportunity`, `exploration_backfill`, after
    `evidence_acquisition` in batch 1).
  * `claims.holder()` returns None by design, and three selftests indexed straight through it.
    Binding and asserting the holder EXISTS is a stronger check than the one-liner it replaced.
  * heterogeneous dict literals whose FIRST entry pins a narrow value type, then conflict later —
    annotated once at the declaration (`decision`, `out`, `checks` x2, `bucket`, `base_kwargs`,
    `candidates`).
  * two lambdas with loop-captured default args, which mypy cannot infer. The capture is
    LOAD-BEARING, so it survives in the replacement functions' signatures.

`mypy_path` gained `tests`, so mypy can RESOLVE the recurrence-fixture roster that
`capability_admission` legitimately imports (declared via `paths.TESTS_DIR`). The check target stays
`src` — but mypy then follows into that one test file, and its two findings were FIXED rather than
excluded. `tick.py` also lost a dead `_unused = ([], [], ...)` tuple my first rewrite left behind.

`mypy_exempt_max` lowered 43 -> 26.

Verified: 442 passed, 0 failed, 0 skipped, 85/85 selftests, 43/43 can-fire, 5/5 gates. `ruff check .`
and `black --check --line-length 100` clean; every touched module's selftest run.

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

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 47 minutes.

View limit details

Limit details: You’ve used the included review currently available. Your 74 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: 8fe4dae5-8469-47d6-b4fc-e7f438318f85

📥 Commits

Reviewing files that changed from the base of the PR and between 4ff9fec and 1175abb.

📒 Files selected for processing (24)
  • .verify-floor.json
  • pyproject.toml
  • src/agent_auth_check.py
  • src/capability_admission.py
  • src/capability_effectiveness.py
  • src/capability_opportunity.py
  • src/ccusage_reconcile.py
  • src/claims.py
  • src/cross_repo_lane.py
  • src/exp_abcd.py
  • src/exploration_backfill.py
  • src/exploration_collection.py
  • src/exploration_evidence_plan.py
  • src/issue_quality.py
  • src/keepalive_evidence.py
  • src/mcp_server.py
  • src/pattern_miner.py
  • src/range_lane_rollout.py
  • src/redirect_apply.py
  • src/research_scheduler.py
  • src/research_subjects.py
  • src/tick.py
  • src/verify.py
  • tests/test_capability_set_coverage.py

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

@agents-workflows-bot

Copy link
Copy Markdown
Contributor

Workflow source needed

PR #95 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
stranske merged commit 1211808 into main Aug 24, 2026
17 checks passed
@stranske
stranske deleted the claude/mypy-drain-batch-2 branch August 24, 2026 03:23
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