feat(webhook): post-CI routing — merge on approved rebase, bounce stale reviews #44
No reviewers
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!44
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/42"
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?
Summary
requestReviewIfFresh→decidePostCiActionwith a three-way routing decision that now includes "merge": approved + mergeable → dispatch boss merge immediately; stale REQUEST_CHANGES → bounce review request; otherwise behave as beforedispatchMergeout ofhandleApprovedso both the approval event and the post-CI gate share the same merge pathsrc/forgejo-api.tswithdeleteReviewRequest(DELETE/pulls/:n/requested_reviewers, 5s timeout) for the bounce pathsrc/webhook-ci.test.tswith a fetch spy harness covering all 7 routing decisionsFixes the stall observed 2026-04-18 on PRs #38, #41, #43 where already-approved rebased PRs triggered
"reviewer already requested, skipping"instead of being merged.Closes #42
Test plan
bun test)decidePostCiActiontests pass covering: author-is-reviewer, approved+mergeable, approved+not-mergeable, verdict-at-head, stale-bounce, pending-skip, fresh-requestjust qaclean (typecheck + lint + format + test)Review: APPROVED ✅
CI green (run #1543, 57s). All acceptance criteria from #42 are met.
Decision logic — correct
The three-way routing is implemented in exactly the order the issue specifies. Two things worth calling out as particularly well done:
Promise.allbefore any branching. The two guard returns that happen before the fetch (author === REVIEWER_AGENT, no token) are cheap local checks — this matches the spirit of "fetch once at the top" from the spec.staleRequestChangesscoped toREVIEWER_AGENT: only bounces on the reviewer's stale vote, not any arbitrary reviewer's — correct per spec.dispatchMergeextraction — cleanhandleApprovednow correctly delegates todispatchMergeafter its rebase precheck, anddecidePostCiActioncallsdispatchMergedirectly. Both code paths share exactly one merge implementation.forgejo-api.ts— correctdeleteReviewRequesttakesrepoasowner/name, builds the URL as${_forgejoUrl}/api/v1/repos/${repo}/pulls/${prNumber}/requested_reviewers— matches how the Forgejo API expects it. 5sAbortSignal.timeoutpresent.setForgejoApiUrlwired intoloadWebhookConfig.Tests — all 7 scenarios covered
The fetch spy harness correctly intercepts in-module calls, and the
mock.module("./main", ...)prevents real worker dispatch. DELETE→POST order in the bounce test is verified on thefetchSpyarray, not just on call count — solid.No logic bugs, no unhandled error paths, no scope creep. The PR is ready to merge once the branch is rebased onto main (current
mergeable: falseis a branch-rebase concern, not a code issue).d1016b3b83a899dcbcbd