Repository navigation
feat(review-desk): Add a ready to merge notice for Junior pull requests - #2492
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
sentry-junior
Bot
force-pushed
the
feat/review-desk-pr-ready
branch
from
November 1, 2026 19:36
a2b9787 to
5030d43
Compare
Add a small provider-neutral contract to the plugin API, so a plugin route can add, update, and close review desk notices for one Conversation (`ctx.reviewItems.forConversation(id).upsert/close`). Core implements it over the review item store. Core scopes dedupe keys to the plugin, sets visibility from the Conversation, assigns the Users of the given Identities, and keeps a private notice for people in its Conversation. The GitHub plugin uses it for Junior pull requests. On `pull_request`, `pull_request_review`, and `check_suite` webhooks it reads the pull request, its reviews, and its check runs from GitHub. When the pull request is open, not a draft, approved, and its checks pass, it adds one notice for the opener in the Conversation where they asked. It closes the notice when the pull request merges, closes, or is no longer ready. `ready_to_merge_at` makes only the call that changes readiness act, so repeat webhooks add no second notice. Part of #2488 Co-Authored-By: David Cramer <[email protected]> Co-Authored-By: David Cramer <[email protected]> Co-Authored-By: David Cramer <[email protected]>
The ready marker changed before the notice closed. A failed close left the marker cleared, so later events saw no change and the notice stayed open. Undo the marker when either the add or the close fails. Co-Authored-By: David Cramer <[email protected]>
Decide readiness from mergeable_state instead of a custom check run list. clean already covers required reviews, checks, commit statuses, and conflicts. An unknown state is read once more. If it stays unknown, the saved state does not change, and a later webhook decides. Keep the approval rule. Tell the person to merge, or to say why not. Inline the subject type of the plugin notice, which has one use. Part of #2488 Co-Authored-By: David Cramer <[email protected]>
Plugin dedupe keys were the plugin name and the key. An agent could use the same free-text key, then update or close the plugin notice from any public Conversation in the tenant. Store plugin keys as plugin:<name>:<key> and reserve the plugin: prefix. addReviewItem rejects a key with the prefix, and an agent cannot change an item that has it. Document that a plugin notice closes quietly: it syncs the Slack cards and sends no message. Part of #2488 Co-Authored-By: David Cramer <[email protected]>
… order A compare-and-set on ready_to_merge_at did not cover the GitHub read or the review item write. Two webhooks that read different states could leave the notice and the saved state out of step, and later webhooks then skipped the repair. Lock the pull request row in one transaction while the webhook reads GitHub, saves the state, and adds or closes the notice. Webhooks for one pull request now run one at a time, and each reads GitHub after the one before it is done. A failed add or close rolls back the saved state. Part of #2488 Co-Authored-By: David Cramer <[email protected]>
…tion The row lock held a pool connection while the webhook called GitHub and the review desk. The review desk write needs a second connection from a pool of three, so a burst of webhooks could wait forever. Claim the check in one short update instead. A webhook that arrives during a check adds a request and stops. The webhook that runs the check reads GitHub again until no new request came in. The request number is the claim id, and a claim older than one minute is free. Save ready_to_merge_at only after the notice changes. Part of #2488 Co-Authored-By: David Cramer <[email protected]>
A webhook that arrived while a ready to merge check failed only raised the request count. The failed check released the claim without another check, so the notice could stay wrong when that webhook was the last event. Now each check attempt ends with the conditional release. Queued requests get a new check, and the first failure is thrown after the last one. Co-Authored-By: David Cramer <[email protected]>
Main now keeps Review item code free of Slack imports (no-review-items-to-slack), and app setup gives the show function. Plugin review items now take that show function from getPluginRoutes instead of syncing Slack cards directly. The README and the updateReviewItem description now list the plugin that added an item as one more way it closes. Co-Authored-By: David Cramer <[email protected]>
sentry-junior
Bot
force-pushed
the
feat/review-desk-pr-ready
branch
from
November 1, 2026 21:51
62d1e32 to
ebd0eac
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ebd0eac. Configure here.
A pull request with a full first page of reviews counted as not approved. So a clean, approved pull request could lose its ready to merge notice. Now the check reads every page, and the latest review of each person decides. Co-Authored-By: David Cramer <[email protected]>
dcramer
approved these changes
Nov 1, 2026
This branch was successfully deployed
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.

Part of #2488 (track 3, scenario C1).
When a pull request that Junior opened is ready to merge, a notice Review item now appears on the review desk for the person who asked for it. The notice tells them to merge the pull request, or to say why not. It closes by itself when the pull request merges, closes, or is no longer ready. "Changes requested" does not add an item.
Plugin contract
packages/junior-plugin-api/src/review-items.tsadds a small provider-neutral contract. Plugins get it asRouteRegistrationHookContext.reviewItems:Core puts this in
packages/junior/src/chat/review-items/plugin.ts, built onreview-items/store.ts. Core scopes the dedupe key to the plugin and takes visibility from the Conversation. It resolves Identities to Users and keeps the rule that a private item can only go to a participant. After each change it calls theshowfunction that app setup passes togetPluginRoutes(today the Slack card sync), so review item code imports no Slack code. A plugin change sends no messages to linked Conversations. A plugin notice never counts as an item that needs a person (reviewItemNeedsUser). Core has no GitHub names or logic.The
plugin:dedupe key prefix belongs to plugins. An agent cannot add an item with that prefix, and it cannot change or close an item that a plugin added (review-items/access.ts,tools/review-items.ts). Only the plugin or a person can. The review items README and theupdateReviewItemdescription list "the plugin that added it closes it" as a close path.How readiness is decided (GitHub plugin)
packages/junior-github/src/webhooks/pull-request-ready.tshandles these webhooks for Junior pull requests:pull_request:closed,converted_to_draft,ready_for_review,reopened,synchronizepull_request_review:submitted,dismissedcheck_suite:completedFor each one,
loadPullRequestMergeStatereads the current state from GitHub with a read-only installation token. The result does not depend on the model. A pull request is ready to merge when all of these are true:GET /pulls/{n}.mergeable_stateisclean. This covers required reviews, checks, commit statuses, and conflicts. GitHub computes this value after a change. When it is stillunknownafter a second read, the plugin changes nothing, and a later webhook decides.APPROVED, and none can beCHANGES_REQUESTED.When the pull request becomes ready, the plugin adds the notice for the opener, in the Conversation where they asked. The dedupe key is the pull request. When it merges, the plugin closes the notice as
done. When it closes or is no longer ready, the plugin closes the notice asdismissed. The requester record now also stores the opener's Conversation, and the create pull request tool fills it in.State and webhooks that arrive together
Migration
0010adds these columns to the stored pull request:ready_to_merge_at: the notice was added. Repeat webhooks add no second notice. A notice that a person closed does not come back until the pull request is ready again. The value changes only after the notice changes, so after a failure the next webhook tries again.ready_sync_requests,ready_sync_id,ready_sync_at: one webhook at a time checks a pull request. A webhook that arrives during a check adds 1 toready_sync_requestsand stops. The webhook that runs the check reads GitHub again until no new request came in, also when a check fails.ready_sync_idnames the check. A check older than one minute no longer blocks others.No database connection or lock stays open during the GitHub and review desk calls. A notice failure is logged and does not fail the webhook.
Checks
pnpm typecheck,pnpm lintpackages/junior-github:pnpm exec vitest run(21 files, 259 tests pass).tests/pull-request-ready.test.tscovers webhooks that arrive during a check, including during a failed close, and an approval on the second page of reviews.pnpm exec vitest run tests/integration/plugin-review-items.test.ts tests/integration/review-item-tools.test.ts tests/integration/review-item-access.test.ts tests/unit/plugins/agent-hooks.test.tstests/integration/api/review-itemsandtests/integration/slack/review-item-card-webhook.test.tsalso pass (64 tests in total with the files above)mainafter ref(review-desk): Deliver every Review item change through one path #2481, fix(review-desk): Use one rule for items that need a person #2503, and fix(review-desk): Close Review items that no agent can act on #2506.via @dcramer.
--
View Session [Sentry]