Post-CI routing: merge when already approved, skip re-review on rebase #42
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#42
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
User story
As a maintainer, when a previously-approved PR is rebased and CI re-goes green, I want the webhook to dispatch boss to merge immediately — not to re-request the reviewer. Re-reviewing an already-approved diff burns Claude tokens for zero value. If the reviewer previously left
REQUEST_CHANGESon a different SHA and is still inrequested_reviewers, bounce them so they re-review the new SHA. If there's no prior verdict, request review as today.Context
Observed 2026-04-18, twice:
REQUEST_CHANGESat old SHA → boss fixed → force-rebased → webhook logged"reviewer already requested, skipping"→ stalled until manual bounce.APPROVEDat old SHA → dev rebased onto new main → CI green → webhook logged"reviewer already requested, skipping"→ stalled even though the PRs were already approved. Manual recovery: directPOST /mergevia boss's token worked first try, no re-review needed. Forgejo does not require commit_id == head on the approval for a squash-merge.Root cause in
src/webhook-ci.ts:requestReviewIfFresh: the decision logic only knows one action — request a review — and its dedup is too aggressive (checksrequested_reviewersbefore checking verdict state). The real "what should happen now" is a three-way routing decision that includes "merge":pull_request_review_request)requested_reviewersAcceptance criteria
Rename + rewire
requestReviewIfFresh→decidePostCiAction(or similar — name reflects that the function now can dispatch a merge too). Keep the export shape thin so callers (handleActionRunEvent,handleStatusEvent,handlePullRequestOpenedno-workflows path) stay one-line.(repo, pr, sha, reason) => Promise<void>. Returns void; all state changes are side effects (dispatch, fetch).Decision logic
verdictAtHead— any APPROVED/REQUEST_CHANGES review withcommit_id === sha.approvedAnywhere— any APPROVED review (any commit_id, not dismissed, by the REVIEWER_AGENT or any reviewer — prefer APPROVED by REVIEWER_AGENT specifically since that's the one our flow cares about).staleRequestChanges— REQUEST_CHANGES review by REVIEWER_AGENT withcommit_id !== sha.reviewerInRequestedList— boolean."... author is reviewer, skipping".approvedAnywhereAND PRmergeable === true→ dispatch boss to merge (reuse the existinghandleApprovedpath or a newdispatchMerge(repo, pr)helper). Log"<reason> on <repo>#<n>@<sha> — approved at <oldSha?>, dispatching boss merge".verdictAtHead→ skip, log"<reason> on <repo>#<n>@<sha> — verdict already at head, skipping".staleRequestChangesANDreviewerInRequestedList→ bounce (DELETE+POSTrequested_reviewers). Log"<reason> on <repo>#<n>@<sha> — stale REQUEST_CHANGES at <oldSha>, bouncing review request".reviewerInRequestedList(and none of the above) → skip, log"<reason> on <repo>#<n>@<sha> — reviewer already requested and pending, skipping".requested_reviewers. Log"<reason> on <repo>#<n>@<sha> — requested review from reviewer".Merge dispatch helper
pull_request_approvedhandler already has merge-dispatch logic, factor it into an exporteddispatchMerge(repo, pr)and call it from bothhandleApprovedand the new decision branch.API wiring
deleteReviewRequest(repo, prNumber, reviewer, token)tosrc/forgejo-api.ts(DELETE/pulls/:n/requested_reviewerswith{reviewers: [...]}). 5sAbortSignal.timeout, shared auth.Tests
In
src/webhook-ci.test.ts(using the fetch harness from #37 now merged):requested_reviewerswrite.mergeable: falsepath is handled elsewhere by the rebase dispatch), no review request either. Log only.verdictAtHead(APPROVED on the current commit) → no dispatches, no fetches beyond the initial PR + reviews list.DELETEthenPOSTcalled in order.POST.Manual verification
"dispatching boss merge"and the PR merges without any reviewer task running."stale REQUEST_CHANGES at <old>, bouncing review request"and the reviewer task runs exactly once.Out of scope
merge.mdorreview.mdskills. This is purely a webhook routing fix.REVIEWER_AGENTusers). The decision should work the same regardless of who left the APPROVED.#40auto-rebase remains a separate concern.References
src/webhook-ci.ts:requestReviewIfFresh.Dependencies
mainlayout is fine).mainRe-dispatch reviewer when head SHA changed and prior verdict is staleto Post-CI routing: merge when already approved, skip re-review on rebase