@@ -49,6 +49,88 @@ func eventKey(event client.Event) string {
4949 return fmt .Sprintf ("%s:%s:%s" , event .Timestamp .Format (time .RFC3339Nano ), event .URL , event .Type )
5050}
5151
52+ // lookupPRsForCheckEvent looks up PRs for a check event that only has a repo URL.
53+ // This happens when sprinkler's cache doesn't know about the PR yet (timing race).
54+ func (c * Coordinator ) lookupPRsForCheckEvent (ctx context.Context , event client.Event , organization string ) []int {
55+ commitSHA := ""
56+ deliveryID := ""
57+ if event .Raw != nil {
58+ if sha , ok := event .Raw ["commit_sha" ].(string ); ok {
59+ commitSHA = sha
60+ }
61+ if id , ok := event .Raw ["delivery_id" ].(string ); ok {
62+ deliveryID = id
63+ }
64+ }
65+
66+ if commitSHA == "" {
67+ slog .Warn ("check event without PR number or commit SHA - cannot look up PRs" ,
68+ "organization" , organization ,
69+ "url" , event .URL ,
70+ "type" , event .Type ,
71+ "delivery_id" , deliveryID )
72+ return nil
73+ }
74+
75+ // Extract owner/repo from URL
76+ parts := strings .Split (event .URL , "/" )
77+ if len (parts ) < 5 || parts [2 ] != "github.com" {
78+ slog .Error ("could not extract repo from check event URL" ,
79+ "organization" , organization ,
80+ "url" , event .URL ,
81+ "error" , "invalid URL format" )
82+ return nil
83+ }
84+ owner := parts [3 ]
85+ repo := parts [4 ]
86+
87+ slog .Info ("sprinkler cache miss - looking up PRs for commit via GitHub API" ,
88+ "organization" , organization ,
89+ "owner" , owner ,
90+ "repo" , repo ,
91+ "commit_sha" , commitSHA ,
92+ "type" , event .Type ,
93+ "delivery_id" , deliveryID )
94+
95+ // Look up PRs for this commit using GitHub API
96+ // Allow up to 3 minutes for retries (2 min max delay + buffer)
97+ lookupCtx , cancel := context .WithTimeout (ctx , 3 * time .Minute )
98+ defer cancel ()
99+
100+ prNumbers , lookupErr := c .github .FindPRsForCommit (lookupCtx , owner , repo , commitSHA )
101+ if lookupErr != nil {
102+ slog .Error ("failed to look up PRs for commit" ,
103+ "organization" , organization ,
104+ "owner" , owner ,
105+ "repo" , repo ,
106+ "commit_sha" , commitSHA ,
107+ "error" , lookupErr ,
108+ "impact" , "will miss this check event" ,
109+ "fallback" , "5-minute polling will catch it" )
110+ return nil
111+ }
112+
113+ if len (prNumbers ) == 0 {
114+ slog .Debug ("no open PRs found for commit - likely push to main" ,
115+ "organization" , organization ,
116+ "owner" , owner ,
117+ "repo" , repo ,
118+ "commit_sha" , commitSHA ,
119+ "type" , event .Type )
120+ return nil
121+ }
122+
123+ slog .Info ("found PRs for commit - processing check event" ,
124+ "organization" , organization ,
125+ "owner" , owner ,
126+ "repo" , repo ,
127+ "commit_sha" , commitSHA ,
128+ "pr_count" , len (prNumbers ),
129+ "pr_numbers" , prNumbers )
130+
131+ return prNumbers
132+ }
133+
52134// handleSprinklerEvent processes a single event from sprinkler.
53135func (c * Coordinator ) handleSprinklerEvent (ctx context.Context , event client.Event , organization string ) {
54136 // Generate event key using delivery_id if available, otherwise timestamp + URL + type.
@@ -130,10 +212,62 @@ func (c *Coordinator) handleSprinklerEvent(ctx context.Context, event client.Eve
130212
131213 prNumber , err := parsePRNumberFromURL (event .URL )
132214 if err != nil {
133- slog .Error ("failed to parse PR number from URL" ,
215+ // Sprinkler sends events with just repo URLs when it can't find PRs for a commit
216+ // For check events, try to recover by looking up PRs ourselves
217+ if event .Type == "check_suite" || event .Type == "check_run" {
218+ prNumbers := c .lookupPRsForCheckEvent (ctx , event , organization )
219+ if prNumbers == nil {
220+ return
221+ }
222+
223+ // Extract owner/repo from URL for constructing PR URLs
224+ parts := strings .Split (event .URL , "/" )
225+ owner := parts [3 ]
226+ repo := parts [4 ]
227+
228+ // Process event for each PR found
229+ for _ , num := range prNumbers {
230+ msg := SprinklerMessage {
231+ Type : event .Type ,
232+ Event : event .Type ,
233+ Repo : owner + "/" + repo ,
234+ PRNumber : num ,
235+ URL : fmt .Sprintf ("https://github.com/%s/%s/pull/%d" , owner , repo , num ),
236+ Timestamp : event .Timestamp ,
237+ }
238+
239+ // Process with timeout
240+ processCtx , processCancel := context .WithTimeout (ctx , 30 * time .Second )
241+ if err := c .processEvent (processCtx , msg ); err != nil {
242+ slog .Error ("error processing recovered check event" ,
243+ "organization" , organization ,
244+ "error" , err ,
245+ "type" , event .Type ,
246+ "url" , msg .URL ,
247+ "repo" , msg .Repo ,
248+ "pr_number" , num )
249+ }
250+ processCancel ()
251+ }
252+ return
253+ }
254+
255+ // Non-check events without PR numbers - just log and ignore
256+ var commitSHA , deliveryID string
257+ if event .Raw != nil {
258+ if sha , ok := event .Raw ["commit_sha" ].(string ); ok {
259+ commitSHA = sha
260+ }
261+ if id , ok := event .Raw ["delivery_id" ].(string ); ok {
262+ deliveryID = id
263+ }
264+ }
265+ slog .Debug ("ignoring event without PR number" ,
134266 "organization" , organization ,
135267 "url" , event .URL ,
136- "error" , err )
268+ "type" , event .Type ,
269+ "commit_sha" , commitSHA ,
270+ "delivery_id" , deliveryID )
137271 return
138272 }
139273
0 commit comments