@@ -403,6 +403,106 @@ describe('Scale down runners', () => {
403403 expect ( mockTerminateRunners ) . toHaveBeenCalledWith ( orphanRunner . instanceId ) ;
404404 } ) ;
405405
406+ it ( 'Should handle 404 error when checking orphaned runner (JIT) - treat as orphaned' , async ( ) => {
407+ // arrange
408+ const orphanRunner = createRunnerTestData (
409+ 'orphan-jit-404' ,
410+ type ,
411+ MINIMUM_BOOT_TIME + 1 ,
412+ false ,
413+ true ,
414+ true , // should be terminated when 404
415+ undefined ,
416+ 1234567890 ,
417+ ) ;
418+ const runners = [ orphanRunner ] ;
419+
420+ mockGitHubRunners ( [ ] ) ;
421+ mockAwsRunners ( runners ) ;
422+
423+ // Mock 404 error response
424+ const error404 = new Error ( 'Runner not found' ) ;
425+ ( error404 as any ) . status = 404 ;
426+
427+ if ( type === 'Repo' ) {
428+ mockOctokit . actions . getSelfHostedRunnerForRepo . mockRejectedValueOnce ( error404 ) ;
429+ } else {
430+ mockOctokit . actions . getSelfHostedRunnerForOrg . mockRejectedValueOnce ( error404 ) ;
431+ }
432+
433+ // act
434+ await scaleDown ( ) ;
435+
436+ // assert - should terminate since 404 means runner doesn't exist on GitHub
437+ expect ( mockTerminateRunners ) . toHaveBeenCalledWith ( orphanRunner . instanceId ) ;
438+ } ) ;
439+
440+ it ( 'Should handle 404 error when checking runner busy state - treat as not busy' , async ( ) => {
441+ // arrange
442+ const runner = createRunnerTestData (
443+ 'runner-404' ,
444+ type ,
445+ MINIMUM_TIME_RUNNING_IN_MINUTES + 1 ,
446+ true ,
447+ false ,
448+ true , // should be terminated since not busy due to 404
449+ ) ;
450+ const runners = [ runner ] ;
451+
452+ mockGitHubRunners ( runners ) ;
453+ mockAwsRunners ( runners ) ;
454+
455+ // Mock 404 error response for busy state check
456+ const error404 = new Error ( 'Runner not found' ) ;
457+ ( error404 as any ) . status = 404 ;
458+
459+ if ( type === 'Repo' ) {
460+ mockOctokit . actions . getSelfHostedRunnerForRepo . mockRejectedValueOnce ( error404 ) ;
461+ } else {
462+ mockOctokit . actions . getSelfHostedRunnerForOrg . mockRejectedValueOnce ( error404 ) ;
463+ }
464+
465+ // act
466+ await scaleDown ( ) ;
467+
468+ // assert - should terminate since 404 means runner is not busy
469+ checkTerminated ( runners ) ;
470+ } ) ;
471+
472+ it ( 'Should re-throw non-404 errors when checking runner state' , async ( ) => {
473+ // arrange
474+ const orphanRunner = createRunnerTestData (
475+ 'orphan-error' ,
476+ type ,
477+ MINIMUM_BOOT_TIME + 1 ,
478+ false ,
479+ true ,
480+ false ,
481+ undefined ,
482+ 1234567890 ,
483+ ) ;
484+ const runners = [ orphanRunner ] ;
485+
486+ mockGitHubRunners ( [ ] ) ;
487+ mockAwsRunners ( runners ) ;
488+
489+ // Mock non-404 error response
490+ const error500 = new Error ( 'Internal server error' ) ;
491+ ( error500 as any ) . status = 500 ;
492+
493+ if ( type === 'Repo' ) {
494+ mockOctokit . actions . getSelfHostedRunnerForRepo . mockRejectedValueOnce ( error500 ) ;
495+ } else {
496+ mockOctokit . actions . getSelfHostedRunnerForOrg . mockRejectedValueOnce ( error500 ) ;
497+ }
498+
499+ // act & assert - should not throw because error handling is in terminateOrphan
500+ await expect ( scaleDown ( ) ) . resolves . not . toThrow ( ) ;
501+
502+ // Should not terminate since the error was not a 404
503+ expect ( mockTerminateRunners ) . not . toHaveBeenCalledWith ( orphanRunner . instanceId ) ;
504+ } ) ;
505+
406506 it ( `Should ignore errors when termination orphan fails.` , async ( ) => {
407507 // setup
408508 const orphanRunner = createRunnerTestData ( 'orphan-1' , type , MINIMUM_BOOT_TIME + 1 , false , true , true ) ;
0 commit comments