perf(lint): skip and cache base gate passes, parallelize make lint, skip redundant prisma generate - #32000
Conversation
…kip redundant prisma generate make pre-commit paid for a full second basedpyright pass over a merge-base worktree on every run even when no rule was over its ceiling, re-generated an unchanged Prisma client, and ran seven independent checks sequentially. The basedpyright and ruff strict gates now skip the base pass when head is within every limit (the same early-out type_discipline_gate already had), the basedpyright base counts are cached under the git common dir keyed by merge-base commit, pyrightconfig.json, and uv.lock, prisma generate only runs when the schema or prisma version changed, and make lint fans its checks out through a parallel sub-make after a single setup phase
Greptile SummaryThis PR significantly cuts
Confidence Score: 5/5All changes are confined to build tooling and lint scripts; production code is untouched and CI behavior is unchanged. The early-outs are provably equivalent to the full evaluation paths, the cache design handles concurrent writers, corrupt entries, and empty-result suppression correctly, and all new logic is covered by thorough tests exercising the failure paths end-to-end. No files require special attention.
|
| Filename | Overview |
|---|---|
| Makefile | Adds parallelized lint execution via sub-make with -j, introduces LINT_DEP_* variables to avoid redundant setup in parallel sub-targets, swaps prisma generate for the new stamping script, and adds output-sync graceful degradation for older make versions. |
| scripts/prisma_generate_if_needed.py | New script that skips prisma generate when schema hash + prisma version stamp matches and the generated client.py exists; correctly invalidates on schema edit, prisma upgrade, or missing client. |
| scripts/ruff_strict_gate.py | Adds over_ceiling early-out that skips the base worktree scan when no budgeted rule is over its limit; behavior is consistent with evaluate which also only iterates over budgeted rules. |
| scripts/type_check_gate.py | Adds over_ceiling early-out (checking unbudgeted rules against DEFAULT_LIMIT, consistent with evaluate) and a disk-based cache for base counts keyed by merge-base commit + pyrightconfig.json + uv.lock fingerprints; scratch files are dot-prefixed and pid-suffixed to avoid prune glob collisions with concurrent writers. |
| scripts/pre_commit_lint.sh | One-line swap of prisma generate for prisma_generate_if_needed.py in the API-type drift block; existing error handling is preserved. |
| tests/test_litellm/test_type_check_gate.py | Comprehensive tests for over_ceiling (including DEFAULT_LIMIT for unbudgeted rules), cache key sensitivity, round-trip/corrupt/misshapen cache reads, prune behavior (spares in-flight scratch, deletes other branch-point entries), and never-cache-empty-pass rule. |
| tests/test_litellm/test_ruff_strict_gate.py | Adds tests for ruff over_ceiling, including unbudgeted-rule ignorance and multi-rule independence; consistent with the gate's policy of ignoring unbudgeted rules. |
| tests/test_litellm/test_prisma_generate_if_needed.py | Tests stamp uniqueness, skip logic requiring both matching stamp and generated client, and the client-absent case forcing regeneration. |
Reviews (3): Last reviewed commit: "fix(lint): keep the base-cache scratch f..." | Re-trigger Greptile
Greptile SummaryThis PR cuts
Confidence Score: 4/5Safe to merge; changes are confined to local developer tooling and have no effect on production code paths or CI verdicts. The correctness of the early-out and caching logic is well-reasoned and thoroughly tested. The one actionable note is that the stale-file prune glob in scripts/type_check_gate.py — the stale-file pruning glob in
|
| Filename | Overview |
|---|---|
| scripts/type_check_gate.py | Adds over_ceiling early-out before the expensive base worktree pass, plus a disk-memoised base_counts_cached. Logic is correct; minor robustness gap in stale-file pruning glob matching the .tmp scratch file. |
| scripts/ruff_strict_gate.py | Adds the same over_ceiling short-circuit as type_check_gate.py. Correctly limited to budget rules (ruff has no DEFAULT_LIMIT concept), consistent with evaluate. No issues. |
| scripts/prisma_generate_if_needed.py | New script that stamps sha256(schema)+prisma_version under sys.prefix and skips prisma generate when both the stamp and generated client are present. Logic is sound; stamp is invalidated on venv recreation, schema change, or version bump. |
| Makefile | Parallelises the seven lint checks via a sub-make -j fan-out; setup prerequisites are zeroed for the sub-make to prevent seven concurrent uv sync/git fetch races. output-sync is feature-detected to degrade gracefully on Apple make 3.81. |
| scripts/pre_commit_lint.sh | Replaces unconditional prisma generate with prisma_generate_if_needed.py in the API-type drift check block. Straightforward swap; no issues. |
| tests/test_litellm/test_type_check_gate.py | Comprehensive new tests: over-ceiling edge cases (at-limit, over-limit, unbudgeted rules with DEFAULT_LIMIT), full cache round-trip, corrupt/misshapen cache resilience, prune-on-write, compute-once-then-hit, and never-cache-empty-pass. Good coverage. |
| tests/test_litellm/test_ruff_strict_gate.py | Adds three tests for the new over_ceiling function covering at-limit, over-limit, missing-from-budget (correctly returns empty set), and cross-rule independence. All correct. |
| tests/test_litellm/test_prisma_generate_if_needed.py | Tests stamp uniqueness, skip-requires-matching-stamp, and skip-requires-generated-client. client_is_generated() and main() are not directly unit-tested, but the testable logic is well covered. |
Reviews (2): Last reviewed commit: "perf(lint): skip and cache base gate pas..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The tmp+rename scratch in store_counts was named basedpyright-base-<hash>.json.tmp, which the stale-entry prune glob (basedpyright-base-*) also matches, so a concurrent lint run from another worktree sharing the same git common dir could unlink it between write_text and replace and crash the gate with FileNotFoundError. The scratch is now dot-prefixed so the glob can never see it, pid-suffixed so concurrent writers of the same entry never share a scratch, and the prune glob is restricted to committed *.json entries
|
Generated by Claude Code |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 9689595. Configure here.
Relevant issues
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
All runs on the same machine, same worktree, with the same staged one-line litellm docstring change (the typical "I touched litellm code and want to commit" scenario)
Before, at the branch point:
After, with this change:
The basedpyright gate goes from always paying a second full pass over a merge-base worktree to skipping it whenever head is within every ceiling. When the base pass is genuinely needed (a rule over its limit), the new cache makes only the first run pay for it; forced that path by temporarily setting reportAny's limit to 0 and piping the same saved head JSON through the gate twice:
Both verdicts identical; the warm run's remaining seconds are parsing the 114MB head JSON from stdin, which every run pays. The cache lives in the git common dir so all worktrees off the same clone share it, and it is keyed by merge-base commit, pyrightconfig.json, and uv.lock, so a rebase, a config change, or a dependency bump each invalidate it
Prisma client regeneration is now stamped and skipped when the schema and prisma version are unchanged:
against ~6s for the unconditional
prisma generateit replaces. On an ungenerated or reinstalled client (fresh venv, prisma version bump, schema edit) it still generates and re-stampsThe failure paths were also exercised end to end, no mocks, to confirm the skip and cache cannot swallow a genuine breach. For the ruff gate: one real ANN001 violation staged and its limit set to the pre-change count; the gate fails through the new early-out with exact blame and lists the introduced violation
For the basedpyright gate: one real type error (
len(1), reportArgumentType) added and that rule's limit set to the base count of 1999; the base counts come from the warm cache (the cache file's mtime is identical before and after the run, so no second basedpyright pass ran) and the verdict is the correct failureThe prisma regenerate paths were exercised the same way: deleting the stamp triggers a real
prisma generate(5.5s) and re-stamps, a matching stamp with a missing generatedclient.py(the reinstalled-package case) still regenerates, and the following run skips in 0.05sType
🚄 Infrastructure
Changes
make pre-commit's dominant cost wasscripts/type_check_gate.pyunconditionally running basedpyright a second time over a detached worktree at the merge-base, on every invocation, even when the verdict could not depend on it. A rule can only breach when its head count is over its limit, so the gate now short-circuits before the base pass when nothing is over ceiling; this is the same early-outscripts/type_discipline_gate.pyalready had, now also applied toscripts/ruff_strict_gate.pywhich paid a smaller version of the same cost (a worktree add per run)When the base pass is needed (some rule over its limit, or
--updateratcheting), its per-rule counts are memoized to/litellm-lint-cache/. The base tree at a given commit is immutable, so the counts are a pure function of the merge-base commit plus the environment, captured in the cache key as sha256 fingerprints ofpyrightconfig.jsonanduv.lock. Stale entries are pruned on write, corrupt or misshapen entries fall back to recomputing, writes are atomic (tmp + rename), and an empty result is never stored since that is the signature of a crashed pass rather than a clean tree, and caching it would blame the next run for every error in the repo. The tmp scratch is dot-prefixed and pid-suffixed so the stale-entry prune glob (now restricted to committed.jsonentries) can never unlink another run's in-flight scratch, which would otherwise crash a concurrent lint from a sibling worktree with FileNotFoundErrormake lintpreviously ran its seven checks sequentially. They are independent, solintnow runs setup once (env sync, Prisma client, base fetch) and then fans the checks out through a$(MAKE) -jsub-make; the sub-make empties the per-check setup prerequisites (LINT_DEP_INSTALL,LINT_DEP_BASE) so seven concurrent checks don't race sevenuv syncs andgit fetches, while standalone targets likemake lint-gatekeep their defaults and behave as before.--output-sync=targetis added only when the running make supports it (Apple's make 3.81 does not; it degrades gracefully)prisma generate(about 6s) ran unconditionally inlint-installand in the pre-commit API-type drift block. The newscripts/prisma_generate_if_needed.pystamps sha256(schema.prisma) plus the prisma package version under the venv prefix and skips generation when the stamp matches and the generated client actually exists; any schema edit, prisma upgrade, venv recreation, or missing client regenerates. It deliberately never imports the prisma package, because once generated the package re-exports the whole client on import, which costs more than the generate being skippedCI is unaffected in behavior: test-linting.yml invokes the gate scripts directly and gets the same verdicts (its runners are fresh, so the cache is simply cold), and it keeps its own unconditional
prisma generate. The gates' new early-out also shaves the same base-pass cost off the CI lint job whenever a PR stays within ceilingsNew tests cover the over-ceiling early-out for both gates (including the default limit for unbudgeted rules), cache key sensitivity to the merge-base and each fingerprint, round-trip plus corrupt/misshapen/missing cache handling, pruning of other branch points' entries while sparing a concurrent run's in-flight scratch, the scratch name staying invisible to the prune glob, hit-without-recompute and compute-once-then-hit behavior via an injected compute function, the never-cache-an-empty-pass rule, and the prisma stamp/skip decision logic
Note
Low Risk
Changes are confined to local lint orchestration and dev tooling; gate pass/fail logic is preserved with explicit early-outs and cache fallbacks, and CI behavior is unchanged when caches are cold.
Overview
Speeds up local
make lintandmake pre-commitwithout changing gate verdicts when CI runs the same scripts.scripts/type_check_gate.pyandscripts/ruff_strict_gate.pynow skip the expensive merge-base worktree scan when every rule is at or under its budget (over_ceilingearly-out). When a base pass is still required, basedpyright base per-rule counts are disk-cached under the git common dir (litellm-lint-cache/), keyed by merge-base,pyrightconfig.json, anduv.lock, with safe concurrent writes and no caching of empty (crashed) results.make lintrunslint-installandlint-fetch-baseonce, then fans outlint-checksin parallel via$(MAKE) -j, clearing per-targetuv sync/ fetch deps so parallel jobs do not race setup.scripts/prisma_generate_if_needed.pyreplaces unconditionalprisma generateinlint-installandpre_commit_lint.sh, regenerating only when schema hash, prisma version, or missingclient.pydemand it.Tests cover ceiling short-circuit, cache behavior, prisma stamp logic, and existing gate semantics.
Reviewed by Cursor Bugbot for commit 9689595. Bugbot is set up for automated code reviews on this repo. Configure here.