Skip to content

fix(review): forced re-run steals the AI-review lock; re-run checkbox ignores closed/merged PRs#9108

Merged
JSONbored merged 1 commit into
mainfrom
fix/9008-9020-rerun-lock-cluster
Jul 26, 2026
Merged

fix(review): forced re-run steals the AI-review lock; re-run checkbox ignores closed/merged PRs#9108
JSONbored merged 1 commit into
mainfrom
fix/9008-9020-rerun-lock-cluster

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Two related fixes to the panel re-run mechanism:

#9008 — forced re-run silently died behind an orphaned AI-review lock. claimAiReviewLock had no bypass, so a maintainer's forced re-run (the panel checkbox / @loopover review) landed on the exact same contended-placeholder path a genuine race would — with zero audit trail, since every ai_review_* event lives inside the lock-acquired branch. Worse, the placeholder's plain-prose notes masqueraded as a real review summary, suppressing the one alarm (ai_review_public_summary_missing) meant to catch "no fresh review happened." Confirmed live: an orphaned lock (from a self-host process that died mid-review) made the re-run button inert for 15+ minutes.

Fix:

  • New steal option on claimTransientLock (cache path: unconditional set instead of claim; SubmissionLock DO: same overwrite semantics), threaded through claimAiReviewLock and used whenever forceAiReview: true — a duplicate LLM call is the explicitly accepted cost.
  • The contended path now always names itself in the audit trail (github_app.ai_review_lock_contended, with a forced flag), forced or not.
  • Fixed the masquerade by checking persistable === false directly in the alarm condition — the one signal aiReviewLockContendedResult uniquely produces.
  • Threaded forceAiReview into shouldStartAiReviewForAdvisory to bypass ONLY the reputation-based skip, never a maintainer-configured hard gate (aiReviewMode off, an ineligible author, etc.).

#9020 — the re-run checkbox does real work (and worse) on an already-merged PR. The panel comment is deliberately preserved after merge (#preserve-review-on-close) and its checkbox stays interactive on GitHub forever, but maybeProcessPrPanelRetrigger never checked pr.state, so a post-merge click ran deep into the pipeline — real GitHub/DB spend — before finally dying at the freshness guard with contradictory telemetry and no user-visible feedback. Second harm: it could reach createOrUpdateSkippedGateCheckRun, which had no updateExisting mode and could PATCH an already-completed, fully-evaluated Gate run down to skipped — falsifying the historical verdict on a merged PR.

Fix: added the same pr.state !== "open" guard reReviewStoredPullRequest already has (mirrors its sibling entry point exactly), and scoped createOrUpdateSkippedGateCheckRun to updateExisting: "in_progress_only" — matching its actual purpose (finalize an in-progress evaluation that never finished) without ever touching an already-terminal run.

Closes #9008, #9020

Test plan

  • 4 new transient-locks.test.ts tests: steal on both the cache and DO paths, a genuine (non-steal) claim still contends normally, steal fails open when the cache errors
  • ai-review-advisory.test.ts: forceAiReview bypasses reputation but not a hard gate
  • queue.test.ts: two full-pipeline integration tests — forced steal + audit event + masquerade fix end to end, plus the audit write's own fail-open branch
  • queue-4.test.ts: a re-run click on a merged PR is skipped immediately with pr_not_open, no live GitHub calls
  • github-app.test.ts: an already-completed Gate check run is never demoted, a separate run is posted instead
  • 100% line+branch coverage on every changed line across all 5 touched source files (verified via lcov)
  • npx tsc --noEmit, check-migrations.ts, check-schema-drift.ts, check-engine-parity.ts all green
  • Full regression pass across all 11 affected test files (1206/1206 passing)

…of silently no-op'ing; re-run checkbox ignores closed/merged PRs

Two related fixes to the panel re-run mechanism:

#9008 -- a forced re-run (the panel checkbox / `@loopover review`) previously
died silently behind the AI-review lock: claimAiReviewLock had no way to
bypass a still-held (possibly orphaned, from a self-host process that died
mid-review) lock, so the contended path returned the same inconclusive
placeholder a genuine race would, with zero audit trail, and that placeholder's
plain-prose notes masqueraded as a real review summary (hasPublicReviewAssessment
had no way to tell it apart), suppressing the one alarm meant to catch this.
shouldStartAiReviewForAdvisory also silently ignored forceAiReview entirely, so
a low-reputation author's forced re-run could skip for a completely different,
unrelated reason.

Adds a `steal` option to claimTransientLock (cache path: unconditional `set`
instead of `claim`; SubmissionLock DO: same overwrite semantics), threaded
through claimAiReviewLock and used whenever `forceAiReview: true`. Names the
contended path in the audit trail unconditionally (forced or not). Fixes the
masquerade by checking `persistable === false` directly -- the one signal
aiReviewLockContendedResult uniquely produces. Threads forceAiReview into
shouldStartAiReviewForAdvisory to bypass ONLY the reputation skip, never a
maintainer-configured hard gate.

#9020 -- the panel comment is deliberately preserved after merge/close and its
re-run checkbox stays interactive on GitHub forever, but maybeProcessPrPanelRetrigger
never checked pr.state, so a post-merge click ran deep into the pipeline (real
GitHub/DB spend) before dying at the freshness guard with contradictory
telemetry and no user feedback. Adds the same `pr.state !== "open"` guard
reReviewStoredPullRequest already has. Separately, createOrUpdateSkippedGateCheckRun
had no updateExisting mode, so its no-checkRunId lookup path could PATCH an
already-`completed` Gate run to `skipped` -- falsifying a fully-evaluated,
merged PR's historical verdict. Scoped to updateExisting: "in_progress_only",
matching this call's actual purpose (finalize an in-progress evaluation that
never finished).

Closes #9008, #9020

Tests: 4 new transient-lock steal tests (cache + DO paths, both success and
fail-open), a forceAiReview-bypasses-reputation test, two full-pipeline
integration tests proving the steal + audit event + masquerade fix end to end
(including the audit write's own fail-open branch), a pr_not_open skip test,
and a completed-check-run-never-demoted test. 100% line+branch coverage on
every changed line across all 5 touched source files.
@JSONbored JSONbored self-assigned this Jul 26, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.66%. Comparing base (f2f32cc) to head (6a8bd8d).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9108      +/-   ##
==========================================
- Coverage   93.88%   92.66%   -1.23%     
==========================================
  Files         813      813              
  Lines       80666    80677      +11     
  Branches    24477    24479       +2     
==========================================
- Hits        75737    74759     -978     
- Misses       3562     4842    +1280     
+ Partials     1367     1076     -291     
Flag Coverage Δ
backend 93.47% <100.00%> (-1.70%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/github/app.ts 97.94% <ø> (ø)
src/queue/ai-review-orchestration.ts 100.00% <100.00%> (ø)
src/queue/processors.ts 95.62% <100.00%> (+<0.01%) ⬆️
src/queue/submission-lock.ts 100.00% <100.00%> (ø)
src/queue/transient-locks.ts 100.00% <100.00%> (ø)

... and 3 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 26, 2026
@loopover-orb

loopover-orb Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-26 19:09:36 UTC

10 files · 1 AI reviewer · no blockers · readiness 88/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR closes #9008 (forced re-run steals an orphaned AI-review lock and now always names itself in the audit trail, plus fixes the lock-contended masquerade via `persistable === false`) and #9020 (panel re-run checkbox now checks `pr.state !== "open"`, and `createOrUpdateSkippedGateCheckRun` is hardened to `updateExisting: "in_progress_only"` so it can never demote an already-completed Gate run). The `steal` option correctly does an unconditional overwrite (`set`/DO overwrite) instead of a CAS `claim`, and `forceAiReview` is threaded through `shouldStartAiReviewForAdvisory` to bypass only the reputation skip, not hard gates — verified in the diff and tests. Tests are substantial and appear to exercise the real paths (orphan-lock steal, contended audit trail, persistable-false alarm, merged-PR panel skip, completed-check-run non-demotion).

Nits — 6 non-blocking
  • src/queue/transient-locks.ts: the steal branch's catch block returns `{ acquired: true, ownerToken: null }` on a `set()` failure — this fails open for a forced re-run, which is consistent with existing claim-failure posture, but worth a one-line note that a failed steal means the caller can't ever release the lock it thinks it claimed (transient-locks.ts).
  • src/queue/ai-review-orchestration.ts: `aiReviewLockContendedResult` sets `persistable: false` but the new gate check `aiReview?.persistable === false` relies on no other producer ever setting that field false — worth a lightweight type-level guarantee (e.g. a shared constant/type) rather than relying on convention across files.
  • src/github/app.ts: the `updateExisting: "in_progress_only"` change to `createOrUpdateSkippedGateCheckRun` silently changes behavior for any other existing caller relying on the previous default 'any' semantics — confirm no other call site depended on demoting a completed run to skipped.
  • Consider extracting a small enum/type for the aiReview result shapes (`persistable: false` vs `cacheable: false`) so future producers can't accidentally omit setting `persistable` and silently defeat the alarm again.
  • Double check all call sites of `createOrUpdateSkippedGateCheckRun` to confirm none intentionally relied on the old 'demote a completed run' behavior.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9008
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ❌ 8/20 High review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 13 registered-repo PR(s), 13 merged, 315 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 13 PR(s), 315 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Addressed
The PR wires forceAiReview through to claimAiReviewLock with a new steal option that unconditionally overwrites the lock (both cache and DO paths), adds an unconditional audit event on the contended path, fixes the masquerade by checking persistable === false in the missing-summary alarm, and threads forceAiReview into shouldStartAiReviewForAdvisory to bypass only the reputation skip — covering al

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 13 PR(s), 315 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 2 steps in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Decision record
  • action: merge · clause: success
  • config: 03a7f8b529a9 · pack: oss-anti-slop
  • record: 33a64a18679d (schema v3, head 6a8bd8d)

🟩 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 LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@JSONbored
JSONbored merged commit db5f7c1 into main Jul 26, 2026
7 checks passed
@JSONbored
JSONbored deleted the fix/9008-9020-rerun-lock-cluster branch July 26, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

orb(review): forceAiReview does not bypass the AI-review lock — the re-run button is silently inert behind an orphaned lock (root cause of #9000)

1 participant