fix(review): claim the AI-review lock before the cache-read, not just the LLM call#4033
Merged
Merged
Conversation
Contributor
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Contributor
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-07 12:20:15 UTC
⏸️ Suggested Action - Manual Review
Nits — 1 non-blocking
Concerns raised — review before merging
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 #4033 +/- ##
=======================================
Coverage 93.58% 93.58%
=======================================
Files 381 381
Lines 35483 35492 +9
Branches 13014 13017 +3
=======================================
+ Hits 33206 33215 +9
Misses 1617 1617
Partials 660 660
🚀 New features to boost your workflow:
|
JSONbored
force-pushed
the
fix/ai-review-lock-covers-cache-check
branch
from
July 7, 2026 11:29
e062607 to
8f4fe67
Compare
JSONbored
force-pushed
the
fix/ai-review-lock-covers-cache-check
branch
from
July 7, 2026 11:48
292b5d1 to
d572bea
Compare
… the LLM call Two near-simultaneous webhook deliveries for the same PR at the same head SHA (JSONbored/awesome-claude#4554: two deliveries ~900ms apart) could each independently reach the AI-review cache-read decision in maybePublishPrPublicSurface before either one ever claimed claimAiReviewLock — that claim lived only deep inside runAiReviewForAdvisory. Both passes logged an identical cache miss and proceeded toward a fresh review, duplicating manifest resolution, review-file loading, and fingerprint computation, and, depending on timing, a real LLM call. The caller now claims the same (repo, PR, head SHA, mode) lock immediately before the cache-read decision, wrapping the whole read-decide-run-persist sequence. A losing pass defers via the shared inconclusive-hold placeholder before ever touching the cache. The winning pass's claim is threaded into runAiReviewForAdvisory (preAcquiredAiReviewLock) so that function trusts it instead of re-claiming — and losing — against its own caller; release stays with the claiming caller so the lock still covers the post-return cache write. Every existing direct caller of runAiReviewForAdvisory is unaffected (the new param is optional and defaults to the prior self-claim/self-release behavior). Closes #4026
JSONbored
force-pushed
the
fix/ai-review-lock-covers-cache-check
branch
from
July 7, 2026 12:11
d572bea to
76c1619
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two near-simultaneous GitHub webhook deliveries for the same PR at the same head SHA (
JSONbored/awesome-claudePR #4554: two deliveries ~900ms apart) could each independently reach the AI-review cache-read decision inmaybePublishPrPublicSurfacebefore either one ever claimedclaimAiReviewLock— that claim lived only deep insiderunAiReviewForAdvisory. Both passes logged an identicalai_review_cache_missaudit event and proceeded toward a fresh review, duplicating manifest resolution, review-file loading, and fingerprint computation, and, depending on exact timing, a real LLM call.The caller (
maybePublishPrPublicSurface) now claims the same(repo, PR, head SHA, mode)lock immediately before the cache-read decision, wrapping the whole read-decide-run-persist sequence instead of only the LLM call at the end of it. A losing pass defers via the shared inconclusive-hold placeholder (aiReviewLockContendedResult, factored out so both lock-claim sites produce byte-identical output) before ever touching the cache. The winning pass's already-acquired claim is threaded intorunAiReviewForAdvisoryvia a new optionalpreAcquiredAiReviewLockparameter, so that function trusts it instead of re-claiming — and always losing — against its own caller. Release stays with the claiming caller (notrunAiReviewForAdvisory's ownfinally) so the lock keeps covering the caller's post-return cache write too, not just the read/run. Every existing direct caller ofrunAiReviewForAdvisoryis unaffected — the new parameter is optional and defaults to the prior self-claim/self-release behavior.Root-caused and reproduced with a regression test that fires two concurrent
agent-regate-prjobs for the same PR/head viaPromise.all, with an artificially delayedenv.AI.runstub (matching a real LLM call's wall-clock latency) to force a genuine race rather than an incidental one — and assertsgithub_app.ai_review_cache_misslands exactly once, not twice. Verified this test fails on the pre-fix code (missAudit?.nis 2) and passes after the fix (missAudit?.nis 1).Closes #4026
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally;codecov/patchrequires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate. — every changed line/branch is covered (100%/100% measured directly from the lcov report, combiningtest/unit/ai-review-advisory.test.tsandtest/unit/queue.test.ts).npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateIf any required check was skipped, explain why:
src/queue/processors.ts), so those checks pass trivially with no relevant diff.Safety
UI Evidencesection below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository. — not applicable; no visible UI change.UI Evidence
Not applicable — backend-only change with no visible UI/frontend/docs surface.
Notes
runAiReviewForAdvisoryso its existing lock naturally wraps it. Moving the ~150-line cache-check-and-persist block intorunAiReviewForAdvisory(option b, literally) would have meant either relocating a lock with 10+ existing passing tests validating its exact atomicity semantics, or introducing a second lock claim on the same key (which would self-contend and always lose against the caller's own claim). Instead, this PR claims the lock in the caller (option a) and threads the already-acquired claim intorunAiReviewForAdvisoryvia a new optional parameter, so the function's existing, well-tested lock logic is reused rather than duplicated or relocated — the caller does the claiming,runAiReviewForAdvisorytrusts a supplied claim instead of re-claiming, and release stays with whichever side did the actual claim. This kept the diff to two files and preserved every existing test's behavior unmodified.