feat(github-events): ingest pull_request_review_thread webhook (#2322) - #2349
feat(github-events): ingest pull_request_review_thread webhook (#2322)#2349lsm wants to merge 4 commits into
Conversation
Subscribe to the pull_request_review_thread (resolved/unresolved) webhook and re-express it as .thread_resolved / .thread_unresolved topics, powering the require-conversation-resolution rule as an event-driven signal. Unlike review-comment events, this payload carries the review-thread node id directly (thread.node_id = PullRequestReviewThread.id), so the normalizer populates a resolveHandle keyed to it without a runtime GraphQL reviewThreads lookup. Resolution toggles recur (resolve -> unresolved -> resolve), so the dedupe identity includes the delivery id, mirroring the pull_request pattern.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Greptile SummaryThis PR wires up the
Confidence Score: 5/5Safe to merge — the change is additive (new webhook event type, new normalization branch, new essence projection) with no modifications to existing event handling paths. Every code path introduced by this PR is exercised by dedicated tests: resolved/unresolved actions, dedupe non-collision across resolution toggles, graceful degradation on absent Files Needing Attention: No files require special attention. The
|
| Filename | Overview |
|---|---|
| packages/daemon/src/lib/external-events/github/github-normalizer.ts | Adds pull_request_review_thread to GitHubEventKind, the accept guard, and the normalization branch; dedupe key includes delivery ID to handle resolution toggles; occurredAt sources from pr.updated_at with comment-timestamp fallbacks; mapEventType maps to thread_resolved/thread_unresolved. |
| packages/daemon/src/lib/external-events/event-essence.ts | Adds a new branch for pull_request_review_thread that projects threadId, path, line, and side into the lean essence, matching the pull_request_review_comment projection pattern. |
| packages/daemon/src/lib/external-events/github/github-event-extension.ts | Adds pull_request_review_thread to WEBHOOK_EVENTS so auto-registered hooks and validateRemoteHook enforce the new subscription. |
| packages/daemon/tests/unit/2-handlers/github/github-normalizer.test.ts | Four new tests cover: resolved/unresolved actions, dedupe non-collision across resolution toggles, graceful degradation when thread.node_id is absent, and occurredAt fallback to root-comment timestamp on thin PR payloads. |
| packages/daemon/tests/unit/2-handlers/github/external-event-essence-contract.test.ts | New essence-contract test asserts threadId, resolveHandle, body, path, line, side are projected and that raw payload sentinel values do not leak into the essence. |
| packages/daemon/tests/unit/2-handlers/github/github-event-extension.test.ts | Integration test delivers a pull_request_review_thread webhook and asserts the .thread_resolved topic and resolveHandle payload; four existing WEBHOOK_EVENTS snapshot assertions updated to include the new event type. |
| packages/web/src/components/space/SpaceExternalEventsSettings.tsx | UI help text updated to mention pull_request_review_thread alongside the other manually-configurable webhook events. |
Reviews (2): Last reviewed commit: "docs(web): list pull_request_review_thre..." | Re-trigger Greptile
…ect side Address review feedback on #2349 (greptile): - occurredAt now keys off pr.updated_at (bumped by the resolution action) with the root-comment timestamps as fallbacks for thin payloads. The previous rootComment.updated_at only tracks comment-text edits, which can be days stale relative to the actual resolve/unresolve transition. - Project `side` into the pull_request_review_thread essence, matching the pull_request_review_comment projection so the full path/line/side location reaches agents.
lsm
left a comment
There was a problem hiding this comment.
🤖 Review by GLM-5.1 (GLM)
Model: GLM-5.1 | Client: NeoKai | Provider: GLM
Recommendation: REQUEST_CHANGES
The ingestion itself is correct and well-tested. I verified independently against GitHub's webhook + GraphQL reference that thread.node_id is genuinely the PullRequestReviewThread.id that resolveReviewThread(input:{threadId}) requires (REST node_id == GraphQL id), so populating resolveHandle without a runtime reviewThreads lookup is sound. The dedupe key correctly anchors on the delivery id, so resolve → unresolved → resolve toggles don't collapse against each other while redeliveries still dedupe. mapEventType, the accept guard, the essence formatter, and WEBHOOK_EVENTS are all updated in lockstep. 195 tests across the three github-handler suites pass.
Two P2 issues block this from landing — both already have correct fixes staged in the worktree (verified green), but the PR head (72fa9fb08) still carries the old code:
- P2 —
occurredAtsourcing (github-normalizer.ts:313, comment).rootComment.updated_atmoves only on comment-body edits, not on resolution; a thread resolved days after its last edit gets a stale timestamp. Preferpr.updated_at(bumped by the resolution action) with the comment timestamps as fallback. - P2 —
sidemissing from the thread essence (event-essence.ts:71, comment). The review-comment essence projectsside; the thread essence does not, though the normalizer populates it. Add'side'for parity.
Action needed: commit + push the two staged fixes and confirm the github-handler tests stay green.
Non-blocking suggestion (P3): packages/web/src/components/space/SpaceExternalEventsSettings.tsx:772 lists the webhook events for manual local-dev setup and omits pull_request_review_thread. That file isn't in this PR's scope, so I'm not blocking on it — but worth a one-line follow-up so the help text matches WEBHOOK_EVENTS.
Out of scope (not a finding): wiring this event into the require-conversation-resolution validator (pr-ready-validator.ts) so it actually reacts to .thread_resolved/.thread_unresolved instead of re-querying reviewThreads — correctly left for a separate task.
Operational note (not a finding): adding the event to WEBHOOK_EVENTS makes it required, so pre-existing remote hooks will surface a "missing events" error on the next checkWebhook until re-applied via autoConfigureWebhook. This is by design and matches how prior event additions rolled out.
The external-events settings help text enumerates the GitHub webhook events the daemon subscribes to; add the newly-ingested pull_request_review_thread so the user-facing list stays accurate.
…vents The commentId field doc listed thread events among the "empty" cases, but the pull_request_review_thread branch populates it with the thread's root comment id; nodeId is the thread node id there too. Bring the field doc in line.
There was a problem hiding this comment.
lsm has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
lsm
left a comment
There was a problem hiding this comment.
🤖 Review by GLM-5.1 (GLM)
Model: GLM-5.1 | Client: NeoKai | Provider: GLM
Recommendation: APPROVE
Re-reviewed fresh at head 09180a357. All prior findings are resolved and I found no new issues.
Verified independently:
thread.node_idis genuinely thePullRequestReviewThread.idthatresolveReviewThread(input:{threadId})requires (RESTnode_id== GraphQLid, confirmed against GitHub's webhook + GraphQL reference), so the no-runtime-lookupresolveHandleis sound.- The dedupe key anchors on the delivery id, so resolve → unresolved → resolve toggles each stay distinct while redeliveries collapse — covered by a dedicated test.
- Both P2s are correctly fixed and now tested:
occurredAtsources frompr.updated_at(bumped by the resolution action) with root-comment timestamps as fallback (the assertion proves it picks the PR's00:10over the comment's older00:05, plus a thin-PR fallback test);sideis projected into the thread essence, matching the review-comment branch. - The P3 help-text and the
commentId/nodeIdfield-doc completeness (the only thing Greptile's re-scan flagged) are both fixed in surgical follow-up commits. - 495 github-handler tests pass;
tsc --buildclean; 4/4 review threads resolved; diff is the feature + fixes + tests + docs across 7 files, no scope creep.
Operational note (not blocking): mergeStateStatus is BLOCKED — expected, since dev is protected and is waiting on review approval + CI. No merge conflicts (mergeable: MERGEABLE).
Out of scope, as noted last round: wiring this event into the pr-ready-validator require-conversation-resolution rule (so it reacts to .thread_resolved/.thread_unresolved instead of re-querying reviewThreads) is correctly left to a separate task.
Subscribes to the
pull_request_review_thread(resolved/unresolved) webhook and re-expresses it as.thread_resolved/.thread_unresolvedtopics —github/owner/repo/pull_request/N.thread_resolved— so the require-conversation-resolution rule can react to thread state changes instead of re-querying thereviewThreadsGraphQL connection each time.Unlike review-comment events, this payload carries the review-thread node id directly (
thread.node_id=PullRequestReviewThread.id), so the normalizer populates aresolveHandlekeyed to it with no runtime lookup. Resolution toggles recur (resolve → unresolved → resolve), so the dedupe identity includes the delivery id, mirroring thepull_requestpattern.Touches the normalizer (
GitHubEventKind, accept guard, normalization branch,mapEventType), the essence formatter (projectsthreadId/path/line), andWEBHOOK_EVENTSso auto-registered hooks receive the event andvalidateRemoteHookenforces it.