fix(mergeability): deferred re-check for async GitHub PR mergeability resolution#1267
Merged
Conversation
Co-authored-by: Cascade Bot <bot@cascade.dev>
Co-authored-by: Cascade Bot <bot@cascade.dev>
Adds TriggerResult.deferredRecheck, GitHubJob.mergeabilityRecheckAttempt, processRouterWebhook branch that schedules a bare BullMQ delayed job via scheduleCoalescedJob, and GitHubRouterAdapter.buildJob changes to omit triggerResult when deferring. CLAUDE.md documents the pattern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tection PRConflictDetectedTrigger now returns deferredRecheck instead of a silent skip when mergeable===null after the synchronous retry budget. processGitHubWebhook gains an isRecheckJob param: when the re-check fires and the trigger still returns deferredRecheck, Sentry captures mergeability_recheck_exhausted. worker-entry.ts passes !!mergeabilityRecheckAttempt as isRecheckJob. CHANGELOG entry added. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…all plans complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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.
Summary
PRConflictDetectedTriggerhad a 2×2s=4s synchronous retry budget waiting for GitHub to resolvePR.mergeable. GitHub frequently takes 10–30s for freshly-opened PRs, and never sends a follow-up webhook when mergeability resolves — so the trigger silently skipped forever (observed live on ucho/PR fix: remove non-functional checkbox syntax from prompt templates #329, 2026-05-07).TriggerResult.deferredRecheck(withagentType: null) when the synchronous budget exhausts whilemergeable === null. The router schedules a bare BullMQ delayed re-check job ~45s later viascheduleCoalescedJob(deduped per PR via coalesceKey). The worker re-dispatches via the trigger registry to get fresh mergeability state.nullafter the deferred re-check fires,processGitHubWebhookSentry-captures under tagmergeability_recheck_exhaustedand logs WARN instead of silently discarding.TriggerResult.deferredRecheckis adapter-agnostic — any trigger handler can use it to schedule deferred bare-job re-dispatch without touching shared router/worker infrastructure.What changed
Types & queue (
src/types/index.ts,src/router/queue.ts):TriggerResult.deferredRecheck?: { delayMs: number; coalesceKey: string }— generic deferred re-check signalGitHubJob.mergeabilityRecheckAttempt?: number— marks deferred re-check jobs so the worker can detect exhaustionRouter (
src/router/webhook-processor.ts,src/router/adapters/github.ts):processRouterWebhookafter theskipReasoncheck: callsscheduleCoalescedJob(job, coalesceKey, delayMs)when a trigger returnsdeferredRecheckwithagentType: null; Sentry-captures on Redis failure without 500ing the webhookGitHubRouterAdapter.buildJobomitstriggerResultand setsmergeabilityRecheckAttempt: 1when building a deferred re-check job (bare job → worker re-dispatches fresh)Trigger (
src/triggers/github/pr-conflict-detected.ts):{ agentType: null, deferredRecheck: { delayMs: 45_000, coalesceKey: '${projectId}:pr-conflict-recheck:${prNumber}' } }instead ofskip()Worker (
src/triggers/github/webhook-handler.ts,src/worker-entry.ts):processGitHubWebhookgainsisRecheckJob?: booleanparam; Sentry-capturesmergeability_recheck_exhaustedwhen re-dispatch returnsdeferredRecheckagainworker-entry.tspasses!!mergeabilityRecheckAttemptasisRecheckJobDocs: CLAUDE.md "Deferred re-check" pattern note, CHANGELOG entry.
Test plan
tests/unit/router/deferred-recheck.test.ts— 11 tests: type fields,buildJobbehavior,processRouterWebhookbranch (scheduleCoalescedJob called, bare job, Sentry on throw, branch guards)tests/unit/triggers/github-webhook-handler-recheck.test.ts— 4 tests: Sentry capture on re-check exhaustion (true/false/non-deferredRecheck/pre-resolved)tests/unit/triggers/pr-conflict-detected.test.ts— updated 1 existing test + 3 new:deferredRecheckshape, coalesceKey, delayMstests/unit/worker-entry.test.ts— 1 new + 1 updated:isRecheckJob=truewhenmergeabilityRecheckAttemptset, existing routing test updated for 7th argnpm run typecheckpassesnpm run lintpasses🤖 Generated with Claude Code