@@ -472,25 +472,45 @@ function formatLaunchctlResultDetail(res: {
472472 return ( res . stderr || res . stdout ) . trim ( ) ;
473473}
474474
475- async function isLaunchAgentProcessRunning ( serviceTarget : string ) : Promise < boolean > {
475+ type LaunchAgentProbeResult =
476+ | { state : "running" }
477+ | { state : "stopped" }
478+ | { state : "not-loaded" }
479+ | { state : "unknown" ; detail ?: string } ;
480+
481+ async function probeLaunchAgentState ( serviceTarget : string ) : Promise < LaunchAgentProbeResult > {
476482 const probe = await execLaunchctl ( [ "print" , serviceTarget ] ) ;
477483 if ( probe . code !== 0 ) {
478- return false ;
484+ if ( isLaunchctlNotLoaded ( probe ) ) {
485+ return { state : "not-loaded" } ;
486+ }
487+ return {
488+ state : "unknown" ,
489+ detail : formatLaunchctlResultDetail ( probe ) || undefined ,
490+ } ;
479491 }
480492 const runtime = parseLaunchctlPrint ( probe . stdout || probe . stderr || "" ) ;
481- return typeof runtime . pid === "number" && runtime . pid > 1 ;
493+ if ( typeof runtime . pid === "number" && runtime . pid > 1 ) {
494+ return { state : "running" } ;
495+ }
496+ return { state : "stopped" } ;
482497}
483498
484- async function waitForLaunchAgentStopped ( serviceTarget : string ) : Promise < boolean > {
499+ async function waitForLaunchAgentStopped ( serviceTarget : string ) : Promise < LaunchAgentProbeResult > {
500+ let lastUnknown : LaunchAgentProbeResult | null = null ;
485501 for ( let attempt = 0 ; attempt < 10 ; attempt += 1 ) {
486- if ( ! ( await isLaunchAgentProcessRunning ( serviceTarget ) ) ) {
487- return true ;
502+ const probe = await probeLaunchAgentState ( serviceTarget ) ;
503+ if ( probe . state === "stopped" || probe . state === "not-loaded" ) {
504+ return probe ;
505+ }
506+ if ( probe . state === "unknown" ) {
507+ lastUnknown = probe ;
488508 }
489509 await new Promise ( ( resolve ) => {
490510 setTimeout ( resolve , 100 ) ;
491511 } ) ;
492512 }
493- return false ;
513+ return lastUnknown ?? { state : "running" } ;
494514}
495515
496516export async function stopLaunchAgent ( { stdout, env } : GatewayServiceControlArgs ) : Promise < void > {
@@ -523,16 +543,23 @@ export async function stopLaunchAgent({ stdout, env }: GatewayServiceControlArgs
523543 throw new Error ( `launchctl stop failed: ${ formatLaunchctlResultDetail ( stop ) } ` ) ;
524544 }
525545
526- if ( ! ( await waitForLaunchAgentStopped ( serviceTarget ) ) ) {
546+ const stopState = await waitForLaunchAgentStopped ( serviceTarget ) ;
547+ if ( stopState . state !== "stopped" && stopState . state !== "not-loaded" ) {
527548 const bootout = await execLaunchctl ( [ "bootout" , serviceTarget ] ) ;
528549 if ( bootout . code !== 0 && ! isLaunchctlNotLoaded ( bootout ) ) {
550+ const reason =
551+ stopState . state === "unknown"
552+ ? `launchctl print could not confirm stop: ${ stopState . detail ?? "unknown error" } `
553+ : "launchctl stop left the service running" ;
529554 throw new Error (
530- `launchctl stop left the service running and launchctl bootout failed: ${ formatLaunchctlResultDetail ( bootout ) } ` ,
555+ `${ reason } ; launchctl bootout failed: ${ formatLaunchctlResultDetail ( bootout ) } ` ,
531556 ) ;
532557 }
533- stdout . write (
534- `${ formatLine ( "Warning" , "launchctl stop did not fully stop the service; used bootout fallback and left service unloaded" ) } \n` ,
535- ) ;
558+ const warning =
559+ stopState . state === "unknown"
560+ ? `launchctl print could not confirm stop; used bootout fallback and left service unloaded: ${ stopState . detail ?? "unknown error" } `
561+ : "launchctl stop did not fully stop the service; used bootout fallback and left service unloaded" ;
562+ stdout . write ( `${ formatLine ( "Warning" , warning ) } \n` ) ;
536563 stdout . write ( `${ formatLine ( "Stopped LaunchAgent (degraded)" , serviceTarget ) } \n` ) ;
537564 return ;
538565 }
0 commit comments