@@ -13,6 +13,8 @@ export type CommandResult = {
1313 stderr : string ;
1414} ;
1515
16+ export const DEFAULT_LOCAL_PROCESS_ATTEMPTS = 3 ;
17+
1618function renderCommandFailure ( cmd : string [ ] , result : CommandResult ) : string {
1719 const parts = [ `command failed (${ result . exitCode } ): ${ cmd . join ( " " ) } ` ] ;
1820 const stdout = result . stdout . trim ( ) ;
@@ -26,13 +28,30 @@ function renderCommandFailure(cmd: string[], result: CommandResult): string {
2628 return parts . join ( "\n\n" ) ;
2729}
2830
31+ export function isRetryableLocalProcessFailure ( result : CommandResult ) : boolean {
32+ const output = `${ result . stderr } \n${ result . stdout } ` ;
33+ return result . exitCode === 127 && / \b E B A D F \b / . test ( output ) && / e p o l l _ c t l / . test ( output ) ;
34+ }
35+
2936/**
3037 * Runs a command as argv, captures stdout/stderr, and never throws on non-zero exit.
3138 *
3239 * The harness uses this as the lowest-level primitive so every provider,
3340 * remote helper, and assertion can decide whether a failure is expected or fatal.
3441 */
3542export async function runAllowFailure ( cmd : string [ ] , options : CommandOptions = { } ) : Promise < CommandResult > {
43+ let result : CommandResult = { exitCode : 127 , stdout : "" , stderr : "command was not attempted" } ;
44+ for ( let attempt = 1 ; attempt <= DEFAULT_LOCAL_PROCESS_ATTEMPTS ; attempt += 1 ) {
45+ result = await runOnceAllowFailure ( cmd , options ) ;
46+ if ( ! isRetryableLocalProcessFailure ( result ) || attempt >= DEFAULT_LOCAL_PROCESS_ATTEMPTS ) {
47+ return result ;
48+ }
49+ await Bun . sleep ( attempt * 500 ) ;
50+ }
51+ return result ;
52+ }
53+
54+ async function runOnceAllowFailure ( cmd : string [ ] , options : CommandOptions = { } ) : Promise < CommandResult > {
3655 return await new Promise < CommandResult > ( ( resolve ) => {
3756 const stdout : Buffer [ ] = [ ] ;
3857 const stderr : Buffer [ ] = [ ] ;
0 commit comments