fix(github): self-heal the reviews cache on the periodic sweep, not just webhooks - #2639
Conversation
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-03 00:46:56 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 6 non-blocking
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2639 +/- ##
=======================================
Coverage 96.09% 96.09%
=======================================
Files 237 237
Lines 26472 26482 +10
Branches 9602 9606 +4
=======================================
+ Hits 25438 25449 +11
+ Misses 425 424 -1
Partials 609 609
🚀 New features to boost your workflow:
|
6f1c422 to
20437ef
Compare
…ust webhooks The durable reviews cache (#2537, PR #2633) is only invalidated by an explicit markPullRequestReviewsInvalidated write on a pull_request_review/synchronize webhook. If that write is ever dropped, nothing else re-checks it: the periodic re-gate sweep only calls refreshPullRequestDetails when slop evidence, the manifest gate, or a pre-merge check path is active, so a "quiet" PR with none of those enabled would carry a stale reviews cache indefinitely with no other convergence path. Extracts the reviewsUpToDate check out of fetchAndStorePullRequestDetails into an exported isReviewsCacheUpToDate predicate (same reviewsSyncedAt vs reviewsInvalidatedAt comparison, now a single authoritative definition), and has the per-PR sweep unit (reReviewStoredPullRequest) check it directly, forcing a refresh when stale and no other reason already triggers one. A failed cache-state read fails open toward "stale" rather than crashing the sweep.
… the new sweep-selfheal test fixtures Two new test fixtures reused the file's existing "installation-token" literal verbatim; every other occurrence in this file is pre-existing context, not a new diff line, so this is the first time it trips the scanner. Renamed to the established "fake-installation-token" convention already used elsewhere in this suite for the same reason. Confirmed clean by running the real scanner against the diff directly.
… from a rebase merge
…s-cache invalidation write Gate review (second pass): isReviewsCacheUpToDate's exact reviewsSyncedAt-vs- reviewsInvalidatedAt comparison is correct when the invalidation write actually happens, but a silently DROPPED markPullRequestReviewsInvalidated write leaves reviewsInvalidatedAt null forever -- with no marker to compare against, the sweep's own quiet-PR self-heal (the prior fix in this PR) never detects that anything changed, since the marker-only check reads "up to date" indefinitely. Moves a 48h bounded-age fallback directly into isReviewsCacheUpToDate (the single shared predicate), rather than duplicating it as a second, separate check in the sweep: an old-enough reviewsSyncedAt is now treated as stale regardless of the invalidation marker, closing the gap for a signal that was never recorded in the first place. Also fixes 5 pre-existing tests in backfill-reviews-cache-scoping.test.ts that seeded a fixed 2026-05-20 sync timestamp without pinning "now," which the new bounded-age check correctly started flagging as stale against real wall-clock time.
20437ef to
1403887
Compare
…ypass telemetry The new test fixtures reused the pre-existing "installation-token" literal verbatim; since this is the first time those specific lines appear as new diff content, the scanner flags it the same way #2639 already worked around this exact false positive — rename the new occurrences to the established "fake-installation-token" convention. Also: a forced re-gate bypass was being counted under the cache-miss metric/audit, conflating "the cache had nothing to serve" with "a caller explicitly opted out" — split it into its own gittensory_ai_review_force_bypass_total counter and github_app.ai_review_force_bypass audit event, and tighten the stale forceAiReview comment.
… unchanged heads (#2667) * fix(review): bound AI review re-spend and public-surface republish on unchanged heads Scheduled re-gate sweeps were re-spending a full AI review on every pass for a PR whose outcome landed in a non-cacheable state (consensus defect / inconclusive / a dynamic-context repo with grounding or RAG enabled) — the durable ai_review_cache correctly never stores those outcomes, so nothing throttled the retry. Root-caused in production: one PR generated 281 AI review calls in 24h at an unchanged head, ~92% of it from the RAG-active unconditional-bypass path. - ai_review_cache gains a `cacheable` column; a non-cacheable outcome (and a dynamic-context result, now bounded rather than unconditionally bypassing the cache) is still persisted for a 30-minute cooldown reuse, never as a durable hit. A lock-contention placeholder is still never persisted at all. - New audit events + counters: ai_review_cache_hit/miss/write_error, ai_review_non_cacheable, agent.sweep.regate_ai_skipped_current, github_app.public_surface_publish_skipped_current. A cache write failure is now observable instead of a silent catch. - A narrow public-surface no-op guard skips republishing a check-run-only repo's completed check when nothing provably changed since the last pass (head match + a live-verified completed check run + no pending refresh signal), falling through to a full republish on any doubt. - `agent-regate-pr` jobs carry an optional `force` flag that bypasses both the cache and the cooldown for an explicit manual re-gate. Validated against production Postgres audit_events/ai_usage_events data for the incident repo/PR before and during the fix. * fix(review): avoid a secret-scanner false positive and split forced-bypass telemetry The new test fixtures reused the pre-existing "installation-token" literal verbatim; since this is the first time those specific lines appear as new diff content, the scanner flags it the same way #2639 already worked around this exact false positive — rename the new occurrences to the established "fake-installation-token" convention. Also: a forced re-gate bypass was being counted under the cache-miss metric/audit, conflating "the cache had nothing to serve" with "a caller explicitly opted out" — split it into its own gittensory_ai_review_force_bypass_total counter and github_app.ai_review_force_bypass audit event, and tighten the stale forceAiReview comment.
Summary
pull_request_review/synchronizewebhook. If that write is ever dropped, nothing else re-checks it — the periodic re-gate sweep only callsrefreshPullRequestDetailswhen slop evidence, the manifest gate, or a pre-merge check path is active, so a "quiet" PR with none of those enabled could carry a stale reviews cache indefinitely.reviewsUpToDatecheck out offetchAndStorePullRequestDetailsinto an exportedisReviewsCacheUpToDatepredicate (samereviewsSyncedAtvsreviewsInvalidatedAtcomparison, now one authoritative definition instead of a duplicated copy).reReviewStoredPullRequest) now checks that predicate directly and forces a refresh when the cache is stale and no other reason already triggers one. A failed cache-state read fails open toward "stale" rather than crashing the sweep.refreshPullRequestDetails(...).catch(() => undefined)— a D1 hiccup never crashes the sweep, but it also means that specific pass can be a no-op; convergence relies on a later sweep pass once the read succeeds.Scope
src/github/backfill.ts,src/queue/processors.ts— no schema/API/wrangler changes, no migration needed (reuses the existingreviewsInvalidatedAt/reviewsSyncedAtcolumns from feat(github): cache PR reviews with webhook-based invalidation #2633).Validation
npm run test:ci(full local gate: actionlint, migrations check, typecheck, unsharded coverage, workers, MCP build/pack, miner build, REES tests, OpenAPI checks, UI lint/typecheck/test/build) — green.npm audit --audit-level=moderate— 0 vulnerabilities.Safety