Repository navigation
fix(ownership): fold repo casing in the ownership-reconciliation pr-fix reads (#1370) - #1373
Conversation
…ix reads (#1370) PrFixQueueItem.repo is canonicalized (trim + lowercase) on every write (normalizeQueueRepo, #1145), but both ownership-reconciliation reads matched on the raw cached repository.fullName: the §8.7 commit-time re-check and the bounded ladder scan. With uppercase-cased cache rows both reads silently found no queue row — the re-check could release a REVIEW_OWNED claim while the queue still owned the PR (stripping the agent/* label), and the ladder under-reported review ownership. Fold both query arguments through normalizeQueueRepo, mirroring the #1357 fix in ownership-inspect, with regression tests asserting the withhold fires and the queries receive the folded value.
There was a problem hiding this comment.
AI Automated Review
Full PR review.
Analysis engine: MiniMax-M3.1-Flash (anthropic) — primary route · pr-reviewer-action v3.7.0
Findings (1 info)
| Severity | Location | Finding |
|---|---|---|
| Info | src/lib/ownership-reconciliation.ts:47 |
The PR body's repository-wide “every read” invariant is not currently true because ownership inspection and stale-item reconciliation still query the queue with raw-cased repo values, although both are outside this PR's scoped reads. |
Recommendation: approve—the scoped normalization and both regression tests correctly preserve review-owned claims.
Claims: 2/4 claims held; 2/4 were violated only by broader repository-state wording, without affecting this PR’s scoped fix. The two ownership-reconciliation reads now use the canonical queue identity, while the repository-wide “every read” claim remains false because ownership-inspect.ts and stale-item reconciliation still contain raw-cased reads.
Linked Issue Fit: Both prFixQueueItem.findFirst reads requested by PR 1370 now fold repository.fullName through normalizeQueueRepo; the added tests exercise the commit-time re-check and ladder scan with mixed-case cached repositories, and no migration was added.
Tool Harness Findings: Repository searches confirmed the two changed reads, the normalizeQueueRepo trim/lowercase implementation, and migration 20261008000000 canonicalizing existing rows. The failed GitHub API lookup prevented independent verification of PR 1357, and the exhausted tool budget limited the exhaustive sibling-call-site audit.
Unknowns or Needs Verification: Whether PR 1357 has merged independently remains unverified; this does not affect the correctness of PR 1370’s two scoped reads.
joryirving
left a comment
There was a problem hiding this comment.
Secondary hard-blocker review on exact head 50b95633ae96eab894401339b0018f9c1ba74438: APPROVE. Both ownership-reconciliation reads now use the same canonical queue identity as the write paths: normalizeQueueRepo(repository.fullName) is applied at the §8.7 commit-time review-ownership re-check and at the bounded ladder scan, while forge-facing repository casing remains untouched. The regressions are load-bearing: the fake uses case-sensitive equality like PostgreSQL, the commit-time test proves an uppercase cached repo cannot cause a review-owned claim to be released, and the scan test captures the actual query arguments and proves the folded identity retains the claim.
The primary's lone info finding is partly stale on this exact base: #1371 is already merged as base 1227acee, so ownership-inspect.ts is no longer a raw-cased queue read. The remaining same-class gap in reconcileStalePrFixItems is real, but this PR body explicitly calls it out and tracks it separately as #1372; it is fail-safe in the direction of retaining stale queue items and is outside #1370's two ownership-release reads. Exact-head primary review and all CI/security/image/smoke checks are green. No independent blocker found.
Closes #1370
What
PrFixQueueItem.repois canonicalized (trim + lowercase vianormalizeQueueRepo, #1145; migration20261008000000backfolded legacy rows) on every write, but bothprFixQueueItem.findFirstreads insrc/lib/ownership-reconciliation.tsmatched on the raw cachedrepository.fullName. When the issue cache carries GitHub's canonical casing (Org/Repo), Postgres case-sensitive=matches no row and both reads silently miss an open PR-fix queue row:REVIEW_OWNEDclaim while the queue still owns the PR, and the release path strips theagent/*label;Fix: fold both query arguments through
normalizeQueueRepo, mirroring the identical fix insrc/lib/ownership-inspect.ts(#1357). No data migration needed.Invariants this change must keep, and every other code path enforcing the same rule
Rule: every read of
PrFixQueueItem.repomust use the folded identity (writes already fold inpr-fix-queue.tsentry points).src/lib/ownership-reconciliation.ts— §8.7 commit-time re-check (tx.prFixQueueItem.findFirst): kept by this PR (fold the query arg).src/lib/ownership-reconciliation.ts— bounded ladder scan (client.prFixQueueItem.findFirst): kept by this PR (fold the query arg).src/lib/ownership-inspect.ts— open-PR-fix evidence read: kept by fix(ownership): fold repo casing in the ownership-inspect pr-fix read like every other queue lookup #1357 (same fold); that fix is a separate PR and this diff does not depend on it.src/lib/pr-fix-queue.ts— all public entry points (enqueuePrFixItem,ackPrFixHandout,markPrFix,requeuePrFixItem, queue lookups, etc.) already foldinput.repovianormalizeQueueRepo(next-task linked-PR follow-up re-hands a dropped task every poll and starves the lane #1145): untouched, unchanged.next-task, history, external-wait, pr-followup ingestion) fold at their entry points: untouched, unchanged.reconcileStalePrFixItems(called bypr-followup/syncandissues/reconcile) queries with raw-cased repo keys and under-reaps stale items for uppercase-cased repos. Fail-safe direction (items stay queued, never a wrong release); filed as follow-up fix(pr-fix): reconcileStalePrFixItems queries the queue with raw repo casing, so stale items are never reaped for uppercase-cased repos #1372.Rule: the fold is scoped to the queue query only — forge label reads/removals (
getLiveIssueLabels,removeIssueLabel) keep using the raw cachedrepository.fullName, since the forge needs its canonical casing. Verified: both folds are confined to thewhere.repoargument.Tests
Two regression tests in
src/lib/ownership-reconciliation.test.ts(fake uses strict===string matching, faithful to Postgres=):review ownership (open PR-fix queue row); zero forge removals, no intent committed.prFixQueueItem.findFirstand asserts the query always receives the folded value (never the raw mixed-case name), and the review-owned claim is retained.Both tests fail against the unfixed source (verified by stashing only the source change: exactly these 2 fail, the other 48 pass) and pass with the fix.
Local validation (run on the integrated head)
npx vitest run src/lib/ownership-reconciliation.test.ts— 50/50 passnpm run test— 5263 passed / 26 skipped / 1 todonpm run typecheck— cleannpm run lint— 0 errors (1 pre-existing unrelated warning insrc/app/login/page.tsx)CI status: not observed from this run (operator verifies after exit).