@@ -194,19 +194,31 @@ export async function updatePRBranch(
194194 }
195195}
196196
197+ export interface WaitForMergeableOptions {
198+ timeoutMs ?: number ;
199+ pollIntervalMs ?: number ;
200+ /** Callback when status changes, for UI updates */
201+ onStatusChange ?: ( status : {
202+ mergeable : boolean | null ;
203+ state : string ;
204+ checksComplete : boolean ;
205+ } ) => void ;
206+ }
207+
197208export function waitForMergeable (
198209 prNumber : number ,
199- options ?: { timeoutMs ?: number ; pollIntervalMs ?: number } ,
210+ options ?: WaitForMergeableOptions ,
200211 cwd = process . cwd ( ) ,
201212) : Promise < Result < { mergeable : boolean ; reason ?: string } > > {
202- const timeoutMs = options ?. timeoutMs ?? 30000 ;
203- const pollIntervalMs = options ?. pollIntervalMs ?? 2000 ;
213+ const timeoutMs = options ?. timeoutMs ?? 300000 ; // 5 minutes default
214+ const pollIntervalMs = options ?. pollIntervalMs ?? 5000 ;
204215
205216 return withGitHub (
206217 cwd ,
207218 "check mergeable status" ,
208219 async ( { octokit, owner, repo } ) => {
209220 const startTime = Date . now ( ) ;
221+ let lastState = "" ;
210222
211223 while ( Date . now ( ) - startTime < timeoutMs ) {
212224 const { data : pr } = await octokit . pulls . get ( {
@@ -215,23 +227,58 @@ export function waitForMergeable(
215227 pull_number : prNumber ,
216228 } ) ;
217229
218- if ( pr . mergeable === true ) {
230+ // mergeable_state values:
231+ // - "clean": can merge, all checks passed
232+ // - "blocked": checks pending or required reviews missing
233+ // - "dirty": has conflicts
234+ // - "unstable": has failing checks but can still merge
235+ // - "unknown": GitHub is computing
236+ const state = pr . mergeable_state || "unknown" ;
237+ const checksComplete = state !== "blocked" && state !== "unknown" ;
238+
239+ // Notify caller of status change
240+ if ( state !== lastState ) {
241+ options ?. onStatusChange ?.( {
242+ mergeable : pr . mergeable ,
243+ state,
244+ checksComplete,
245+ } ) ;
246+ lastState = state ;
247+ }
248+
249+ // "clean" means mergeable AND all required checks passed
250+ if ( state === "clean" && pr . mergeable === true ) {
219251 return { mergeable : true } ;
220252 }
221253
222- if ( pr . mergeable === false ) {
254+ // "unstable" means checks failed but PR is still mergeable (non-required checks)
255+ if ( state === "unstable" && pr . mergeable === true ) {
256+ return { mergeable : true } ;
257+ }
258+
259+ // Has conflicts
260+ if ( state === "dirty" ) {
261+ return {
262+ mergeable : false ,
263+ reason : "Has merge conflicts" ,
264+ } ;
265+ }
266+
267+ // Explicit not mergeable
268+ if ( pr . mergeable === false && state !== "unknown" ) {
223269 return {
224270 mergeable : false ,
225- reason : pr . mergeable_state || "Has conflicts or other issues " ,
271+ reason : state || "Not mergeable " ,
226272 } ;
227273 }
228274
275+ // "blocked" or "unknown" - keep waiting
229276 await new Promise ( ( resolve ) => setTimeout ( resolve , pollIntervalMs ) ) ;
230277 }
231278
232279 return {
233280 mergeable : false ,
234- reason : "Timeout waiting for merge status " ,
281+ reason : "Timeout waiting for CI checks " ,
235282 } ;
236283 } ,
237284 ) ;
0 commit comments