@@ -1908,12 +1908,13 @@ async function sandboxStatus(sandboxName: string) {
19081908}
19091909
19101910function sandboxLogs ( sandboxName : string , follow : boolean ) {
1911- enableSandboxAuditLogs ( sandboxName ) ;
19121911 if ( follow ) {
19131912 streamSandboxFollowLogs ( sandboxName ) ;
1913+ enableSandboxAuditLogsInBackground ( sandboxName ) ;
19141914 return ;
19151915 }
19161916
1917+ enableSandboxAuditLogs ( sandboxName ) ;
19171918 runOpenclawGatewayLogs ( sandboxName , false ) ;
19181919 const args = buildSandboxLogsArgs ( sandboxName , false ) ;
19191920 const result = runOpenshell ( args , {
@@ -1980,6 +1981,8 @@ function streamSandboxFollowLogs(sandboxName: string): void {
19801981 let exiting = false ;
19811982 let completedSources = 0 ;
19821983 let finalStatus = 0 ;
1984+ let requestedExitCode : number | null = null ;
1985+ let forcedExitTimer : NodeJS . Timeout | null = null ;
19831986
19841987 const stopChildren = ( signal : NodeJS . Signals ) => {
19851988 for ( const { child } of sources ) {
@@ -1988,6 +1991,16 @@ function streamSandboxFollowLogs(sandboxName: string): void {
19881991 }
19891992 }
19901993 } ;
1994+ const maybeExit = ( ) => {
1995+ if ( completedSources !== sources . length ) {
1996+ return ;
1997+ }
1998+ if ( forcedExitTimer ) {
1999+ clearTimeout ( forcedExitTimer ) ;
2000+ forcedExitTimer = null ;
2001+ }
2002+ process . exit ( requestedExitCode ?? finalStatus ) ;
2003+ } ;
19912004 const exitFromSignal = ( signal : NodeJS . Signals | null ) : number => {
19922005 if ( ! signal ) return 1 ;
19932006 const signalNumber = os . constants . signals [ signal ] ;
@@ -1998,30 +2011,33 @@ function streamSandboxFollowLogs(sandboxName: string): void {
19982011 status : number ,
19992012 detail : string | null = null ,
20002013 ) => {
2001- if ( source . done || exiting ) return ;
2014+ if ( source . done ) return ;
20022015 source . done = true ;
20032016 completedSources += 1 ;
20042017 if ( status !== 0 && finalStatus === 0 ) {
20052018 finalStatus = status ;
20062019 }
2007- if ( completedSources < sources . length ) {
2020+ if ( completedSources < sources . length && ! exiting ) {
20082021 const suffix = detail || `exit ${ status } ` ;
20092022 console . error ( ` ${ source . label } stopped (${ suffix } ); continuing with remaining log source.` ) ;
20102023 }
2011- if ( completedSources === sources . length ) {
2012- process . exit ( finalStatus ) ;
2013- }
2024+ maybeExit ( ) ;
2025+ } ;
2026+ const requestExitAfterSignal = ( signal : NodeJS . Signals , exitCode : number ) => {
2027+ if ( requestedExitCode !== null ) return ;
2028+ exiting = true ;
2029+ requestedExitCode = exitCode ;
2030+ stopChildren ( signal ) ;
2031+ forcedExitTimer = setTimeout ( ( ) => process . exit ( exitCode ) , 2000 ) ;
2032+ forcedExitTimer . unref ?.( ) ;
2033+ maybeExit ( ) ;
20142034 } ;
20152035
20162036 process . once ( "SIGINT" , ( ) => {
2017- exiting = true ;
2018- stopChildren ( "SIGINT" ) ;
2019- process . exit ( 130 ) ;
2037+ requestExitAfterSignal ( "SIGINT" , 130 ) ;
20202038 } ) ;
20212039 process . once ( "SIGTERM" , ( ) => {
2022- exiting = true ;
2023- stopChildren ( "SIGTERM" ) ;
2024- process . exit ( 143 ) ;
2040+ requestExitAfterSignal ( "SIGTERM" , 143 ) ;
20252041 } ) ;
20262042
20272043 for ( const source of sources ) {
@@ -2035,23 +2051,78 @@ function streamSandboxFollowLogs(sandboxName: string): void {
20352051}
20362052
20372053function enableSandboxAuditLogs ( sandboxName : string ) {
2038- const args = [ "settings" , "set" , sandboxName , "--key" , "ocsf_json_enabled" , "--value" , "true" ] ;
2054+ const args = buildEnableSandboxAuditLogsArgs ( sandboxName ) ;
20392055 const result = runOpenshell ( args , {
20402056 stdio : [ "ignore" , "ignore" , "pipe" ] ,
20412057 ignoreError : true ,
20422058 timeout : getLogsProbeTimeoutMs ( ) ,
20432059 } ) ;
20442060 if ( result . status !== 0 ) {
2045- const stderr = String ( result . stderr || "" ) . trim ( ) ;
2046- console . error (
2047- ` Warning: failed to enable OpenShell audit logs for sandbox '${ sandboxName } ' ` +
2048- `(${ describeLogProbeResult ( result ) } ): openshell ${ args . join ( " " ) } ` ,
2049- ) ;
2050- if ( stderr ) {
2051- console . error ( ` ${ stderr } ` ) ;
2061+ warnSandboxAuditLogsUnavailable ( sandboxName , args , result ) ;
2062+ }
2063+ }
2064+
2065+ function enableSandboxAuditLogsInBackground ( sandboxName : string ) : void {
2066+ const args = buildEnableSandboxAuditLogsArgs ( sandboxName ) ;
2067+ const child = spawn ( getOpenshellBinary ( ) , args , {
2068+ cwd : ROOT ,
2069+ env : process . env ,
2070+ stdio : [ "ignore" , "ignore" , "pipe" ] ,
2071+ } ) ;
2072+ let stderr = "" ;
2073+ let timedOut = false ;
2074+ let reported = false ;
2075+ const timeout = setTimeout ( ( ) => {
2076+ timedOut = true ;
2077+ child . kill ( "SIGTERM" ) ;
2078+ } , getLogsProbeTimeoutMs ( ) ) ;
2079+ timeout . unref ?.( ) ;
2080+
2081+ child . stderr ?. on ( "data" , ( chunk : Buffer | string ) => {
2082+ stderr += chunk . toString ( ) ;
2083+ } ) ;
2084+ child . on ( "error" , ( error : Error ) => {
2085+ clearTimeout ( timeout ) ;
2086+ reported = true ;
2087+ warnSandboxAuditLogsUnavailable ( sandboxName , args , {
2088+ status : null ,
2089+ stderr,
2090+ error,
2091+ } ) ;
2092+ } ) ;
2093+ child . on ( "close" , ( code : number | null , signal : NodeJS . Signals | null ) => {
2094+ clearTimeout ( timeout ) ;
2095+ if ( reported || code === 0 ) {
2096+ return ;
20522097 }
2053- console . error ( " Policy denial events may be missing from OpenShell logs." ) ;
2098+ reported = true ;
2099+ warnSandboxAuditLogsUnavailable ( sandboxName , args , {
2100+ status : code ,
2101+ stderr,
2102+ signal,
2103+ error : timedOut ? new Error ( `openshell ${ args . join ( " " ) } timed out` ) : undefined ,
2104+ } ) ;
2105+ } ) ;
2106+ }
2107+
2108+ function warnSandboxAuditLogsUnavailable (
2109+ sandboxName : string ,
2110+ args : string [ ] ,
2111+ result : SpawnLikeResult ,
2112+ ) : void {
2113+ const stderr = String ( result . stderr || "" ) . trim ( ) ;
2114+ console . error (
2115+ ` Warning: failed to enable OpenShell audit logs for sandbox '${ sandboxName } ' ` +
2116+ `(${ describeLogProbeResult ( result ) } ): openshell ${ args . join ( " " ) } ` ,
2117+ ) ;
2118+ if ( stderr ) {
2119+ console . error ( ` ${ stderr } ` ) ;
20542120 }
2121+ console . error ( " Policy denial events may be missing from OpenShell logs." ) ;
2122+ }
2123+
2124+ function buildEnableSandboxAuditLogsArgs ( sandboxName : string ) : string [ ] {
2125+ return [ "settings" , "set" , sandboxName , "--key" , "ocsf_json_enabled" , "--value" , "true" ] ;
20552126}
20562127
20572128function buildSandboxOpenclawGatewayLogsArgs ( sandboxName : string , follow : boolean ) : string [ ] {
0 commit comments