Skip to content

feat(kix): flag a missing review in the inbox before the merge click - #436

Merged
kixelated merged 4 commits into
mainfrom
quest/m1/kix/review-flag
Nov 3, 2026
Merged

kixelated merged 4 commits into
mainfrom
quest/m1/kix/review-flag

Conversation

@kixelated

@kixelated kixelated commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Implements and finishes quest/m1/kix/review-flag.md, a follow-up from moq-dev/quest#351. It deletes the quest and its link.

Summary

Since moq-dev/quest#351, the inbox's Merge this PR refuses an unreviewed head, but only after the click. Now each PR item whose merge is offered shows, beside the button, the reason the land gate would give. The click's live check (unreviewed) stays the authority.

  • Where the data comes from. Each project read already sent one batched GraphQL request for open drafts' bodies. It now covers every open PR, and adds:

    • the author's association with the repository;
    • the writers' latest opinionated reviews (writersOnly, the same field the gate reads);
    • the head commit's parent count;
    • this App's quest review check runs on the head.

    The read keeps only the computed reason (Pull.unreviewed), never the body. Metadata and history pages still never request bodies.

  • Shared rules (cloudflare/src/github/review.ts): the gate's approval filter, its ## Review record parser, and its refusal wording move into helpers. The gate and the new observedGap both use them, so the inbox's reason is the gate's text.

  • What observedGap counts: what the gate counts on the head itself.

    • A writer's approval of the head.
    • A passing quest review check run by the App.
    • The author's own record naming the head. It counts unless the association (anything but OWNER, MEMBER, or COLLABORATOR) rules out write access, or someone else last edited the body.
    • On a merge head, an earlier review may still carry across the merge, which only the gate's tree reads can tell. The reason says so instead of claiming the head is unreviewed.
  • Staying current (cloudflare/src/github/webhook.ts): CI's check_run events still cost no read. A check_run from this App named quest review marks the PRs its payload lists, on this repository, for a reread by number. So a runner's review (moq-dev/quest#417) clears the flag without a refresh. Reviews and body edits already trigger reads through pull_request* events.

  • The fake GitHub (cloudflare/dev/github.ts) models the batch's new fields: the association, the App's check, and the parent count. Its routing for the gate's query, and the two test mocks' routing, now match the gate's query exactly. Before, they matched any query containing latestOpinionatedReviews(, which the batch now also contains.

  • cloudflare/GITHUB.md documents the flag, its limits, and the check-run read.

Choices made without asking

  • Cost: one GraphQL request per read round, whatever the number of PRs. Before, it was sent only when a draft was open. One invocation reads at most three rounds, so this adds at most three subrequests. The alternative was to put bodies in the metadata pages. I rejected it because those pages also page closed history, which deliberately never requests bodies.
  • The flag shows only where merge is offered: open, non-draft, idle items. It sits in the actions row, as the existing blocked reason does. The Review: meta line still shows GitHub's reviewDecision.
  • The author's write access comes from authorAssociation. The gate's REST permission read is per author, so it would cost subrequests. authorAssociation can only rule write access out. When it leaves write access possible, the inbox doesn't flag, and the click confirms.
  • The query's check filter: checkSuites(filterBy:{appId}), then checkRuns(filterBy:{checkName:"quest review",checkType:LATEST}). On suites, checkName filters by the suite's name, not a run's. I checked this against GitHub's real API on this repository, and the real API accepted the whole batch query.
  • The webhook reread checks the PR's base repository (pull_requests[].base.repo.id), so a fork's PR listed in the payload is not marked.

Limitations

  • Rulesets are not read. Where a ruleset that the App cannot bypass makes GitHub require the review, the gate defers to GitHub. The inbox flags the head anyway, and GitHub refuses the same unreviewed head.
  • Earlier reviews: the inbox doesn't see quest review checks or records on commits before a merge head. A merge head's reason says that a review may still carry.
  • A record on an App-committed head: the inbox doesn't check who committed the head, so it shows no flag for an author's record on a head an App committed. The click refuses that head.
  • One-time unread items: the first read after deploy adds unreviewed to every open PR's stored row. Its data changes once, so each open PR item shows unread once, and a skipped one comes back once.

Validation

  • cloudflare/test/review.test.ts: observedGap cases.
    • No flag for an approval, a passing check, or a member's own record on the head.
    • The gate's exact text when no review counts, and the approval-doesn't-carry text.
    • A failing or in-progress check doesn't count.
    • The record doesn't count for a contributor, someone else's last edit, or another SHA.
    • A merge head gets the hedged reason.
    • A read of another head, or one missing the reviews, flags nothing.
  • cloudflare/test/projects.test.ts:
    • the batch reads every open PR's body, draft or not, with the App's ID;
    • a read stores the reason and keeps no body;
    • only this App's quest review check run, on this repository's PR, triggers a reread. CI, another App's quest review, and another repository's PR don't.
  • cloudflare/test/inbox.test.ts: the reason renders escaped, before Merge this PR, and the click still refuses.
  • just check and just test pass locally on a4551a3: 995 CLI tests (2 skipped), and 517 Worker tests.

Suggested follow-ups

None worth a quest.

Review

  • Reviewer: Codex gpt-6.1-sol, medium effort, quest review --base origin/main, session 01a1a276.
  • Round 1 on head a4551a3 (a merge of origin/main at 4c259ba, resolving import conflicts in cloudflare/src/github/service.ts and cloudflare/src/github/webhook.ts by keeping both sides: this PR's OBSERVED/Observed/observedGap/REVIEW_CHECK imports beside main's ./setup and admitted imports): no findings. The PR's added and removed lines are identical to e748416's; only context changed. Main has since gained 66ab4fb and 4c728f1, which merge without conflict.
  • Earlier: session 01a1a255, round 1 on head e748416: no findings; session 01a19f96, round 1 on head 12f9b2e: no findings.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 2 commits November 2, 2026 19:43
Each project read now takes every open PR's body, author association,
writers' latest reviews, and the head commit's parents and the App's
`quest review` check in the one batched GraphQL request that already
classified drafts. It keeps only the land gate's reason, which the inbox
shows beside Merge this PR. The click's live check stays the authority.

The App's own `quest review` check run now asks for a read of the PRs it
names, so a runner's review clears the flag without a refresh.

Finishes quest/m1/kix/review-flag.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title The inbox flags a missing review before the merge click feat(kix): flag a missing review in the inbox before the merge click Nov 3, 2026
@kixelated
kixelated marked this pull request as ready for review November 3, 2026 04:09
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 37 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5c1e0808-bebe-4cb8-895c-fa405af33284
Commits

Reviewing files that changed from the base of the PR and between 4c259ba and a4551a3.

Files selected for processing: 14
  • cloudflare/GITHUB.md
  • cloudflare/dev/github.ts
  • cloudflare/src/github/review.ts
  • cloudflare/src/github/service.ts
  • cloudflare/src/github/state.ts
  • cloudflare/src/github/webhook.ts
  • cloudflare/src/inbox/page.tsx
  • cloudflare/test/github.test.ts
  • cloudflare/test/inbox.test.ts
  • cloudflare/test/project-fixtures.ts
  • cloudflare/test/projects.test.ts
  • cloudflare/test/review.test.ts
  • quest/m1/kix/README.md
  • quest/m1/kix/review-flag.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

# Conflicts:
#	cloudflare/src/github/service.ts
# Conflicts:
#	cloudflare/src/github/service.ts
#	cloudflare/src/github/webhook.ts
@kixelated
kixelated added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit f3b1d04 Nov 3, 2026
10 checks passed
@kixelated
kixelated deleted the quest/m1/kix/review-flag branch November 3, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant