@@ -9,15 +9,13 @@ import {
99 ensureSocketDir ,
1010 removeStaleSocket ,
1111 getSocketPath ,
12- getWorkspaceKey ,
13- resolveWorkspaceRoot ,
1412 logPathForWorkspaceKey ,
1513} from './daemon/socket-path.ts' ;
1614import { startDaemonServer } from './daemon/daemon-server.ts' ;
1715import {
16+ acquireDaemonRegistryMutationLock ,
1817 writeDaemonRegistryEntry ,
19- removeDaemonRegistryEntry ,
20- cleanupWorkspaceDaemonFiles ,
18+ type DaemonRegistryMutationLock ,
2119} from './daemon/daemon-registry.ts' ;
2220import { log , normalizeLogLevel , setLogFile , setLogLevel } from './utils/logger.ts' ;
2321import { version } from './version.ts' ;
@@ -42,11 +40,11 @@ import {
4240} from './utils/sentry.ts' ;
4341import { isXcodemakeBinaryAvailable , isXcodemakeEnabled } from './utils/xcodemake/index.ts' ;
4442import { hydrateSentryDisabledEnvFromProjectConfig } from './utils/sentry-config.ts' ;
45- import { configureRuntimeWorkspaceKey } from './utils/runtime-instance.ts' ;
4643import {
47- reconcileSimulatorLaunchOsLogOrphansForWorkspace ,
48- terminateLiveSimulatorLaunchOsLogSessionsSync ,
49- } from './utils/log-capture/index.ts' ;
44+ cleanupOwnedWorkspaceFilesystemArtifacts ,
45+ runWorkspaceFilesystemLifecycleSweep ,
46+ terminateOwnedWorkspaceFilesystemArtifactsSync ,
47+ } from './utils/workspace-filesystem-lifecycle.ts' ;
5048
5149async function checkExistingDaemon ( socketPath : string ) : Promise < boolean > {
5250 return new Promise< boolean > ( ( resolve ) => {
@@ -124,16 +122,7 @@ async function main(): Promise<void> {
124122 } ,
125123 } ) ;
126124
127- const workspaceRoot = resolveWorkspaceRoot ( {
128- cwd : result . runtime . cwd ,
129- projectConfigPath : result . configPath ,
130- } ) ;
131-
132- const workspaceKey = getWorkspaceKey ( {
133- cwd : result . runtime . cwd ,
134- projectConfigPath : result . configPath ,
135- } ) ;
136- configureRuntimeWorkspaceKey ( workspaceKey ) ;
125+ const { workspaceRoot, workspaceKey } = result ;
137126
138127 const logPath = resolveDaemonLogPath ( workspaceKey ) ;
139128 if ( logPath ) {
@@ -159,20 +148,27 @@ async function main(): Promise<void> {
159148
160149 log ( 'info' , `[Daemon] Workspace: ${ workspaceRoot } ` ) ;
161150 log ( 'info' , `[Daemon] Socket: ${ socketPath } ` ) ;
162- try {
163- const reconciliation = await reconcileSimulatorLaunchOsLogOrphansForWorkspace ( workspaceKey ) ;
164- if ( reconciliation . stoppedSessionCount > 0 || reconciliation . errorCount > 0 ) {
151+
152+ const runStartupLifecycleSweep = async ( ) : Promise < void > => {
153+ try {
154+ const lifecycle = await runWorkspaceFilesystemLifecycleSweep ( {
155+ workspaceKey,
156+ trigger : 'startup' ,
157+ } ) ;
158+ if ( lifecycle . stopped > 0 || lifecycle . deleted > 0 || lifecycle . errors . length > 0 ) {
159+ log (
160+ lifecycle . errors . length > 0 ? 'warn' : 'info' ,
161+ `[Daemon] Filesystem lifecycle: ${ JSON . stringify ( lifecycle ) } ` ,
162+ ) ;
163+ }
164+ } catch ( error ) {
165165 log (
166- reconciliation . errorCount > 0 ? 'warn' : 'info ',
167- `[Daemon] Simulator OSLog reconciliation : ${ JSON . stringify ( reconciliation ) } ` ,
166+ 'warn' ,
167+ `[Daemon] Filesystem lifecycle failed : ${ error instanceof Error ? error . message : String ( error ) } ` ,
168168 ) ;
169169 }
170- } catch ( error ) {
171- log (
172- 'warn' ,
173- `[Daemon] Simulator OSLog reconciliation failed: ${ error instanceof Error ? error . message : String ( error ) } ` ,
174- ) ;
175- }
170+ } ;
171+
176172 if ( logPath ) {
177173 log ( 'info' , `[Daemon] Logs: ${ logPath } ` ) ;
178174 }
@@ -187,6 +183,28 @@ async function main(): Promise<void> {
187183 process . exit ( 1 ) ;
188184 }
189185
186+ const startupRegistryLock = acquireDaemonRegistryMutationLock ( workspaceKey ) ;
187+ if ( ! startupRegistryLock ) {
188+ log ( 'error' , '[Daemon] Unable to acquire daemon registry lock' ) ;
189+ console . error ( 'Error: Unable to acquire daemon registry lock' ) ;
190+ await flushAndCloseSentry ( 1000 ) ;
191+ process . exit ( 1 ) ;
192+ }
193+ let pendingStartupRegistryLock : DaemonRegistryMutationLock | null = startupRegistryLock ;
194+ const releaseStartupRegistryLock = ( ) : void => {
195+ pendingStartupRegistryLock?. release ( ) ;
196+ pendingStartupRegistryLock = null ;
197+ } ;
198+
199+ const isRunningAfterLock = await checkExistingDaemon ( socketPath ) ;
200+ if ( isRunningAfterLock ) {
201+ releaseStartupRegistryLock ( ) ;
202+ log ( 'error' , '[Daemon] Another daemon is already running for this workspace' ) ;
203+ console . error ( 'Error: Daemon is already running for this workspace' ) ;
204+ await flushAndCloseSentry ( 1000 ) ;
205+ process . exit ( 1 ) ;
206+ }
207+
190208 removeStaleSocket ( socketPath ) ;
191209
192210 const excludedWorkflows = [ 'session-management' , 'workflow-discovery' ] ;
@@ -302,26 +320,33 @@ async function main(): Promise<void> {
302320 recordDaemonLifecycleMetric ( 'shutdown' ) ;
303321 log ( 'info' , '[Daemon] Shutting down...' ) ;
304322
305- // Close the server
323+ const cleanupArtifacts = ( ) : Promise < unknown > =>
324+ cleanupOwnedWorkspaceFilesystemArtifacts ( {
325+ workspaceKey,
326+ trigger : 'shutdown' ,
327+ daemonCleanup : {
328+ pid : process . pid ,
329+ socketPath,
330+ allowLiveOwner : true ,
331+ } ,
332+ } ) ;
333+
306334 server . close ( ( ) => {
307335 log ( 'info' , '[Daemon] Server closed' ) ;
308-
309- // Remove registry entry and socket
310- removeDaemonRegistryEntry ( workspaceKey ) ;
311- removeStaleSocket ( socketPath ) ;
312-
313- log ( 'info' , '[Daemon] Cleanup complete' ) ;
314- void flushAndCloseSentry ( 2000 ) . finally ( ( ) => {
315- process . exit ( exitCode ) ;
336+ void cleanupArtifacts ( ) . finally ( ( ) => {
337+ log ( 'info' , '[Daemon] Cleanup complete' ) ;
338+ void flushAndCloseSentry ( 2000 ) . finally ( ( ) => {
339+ process . exit ( exitCode ) ;
340+ } ) ;
316341 } ) ;
317342 } ) ;
318343
319- // Force exit if server doesn't close in time
320344 setTimeout ( ( ) => {
321345 log ( 'warn' , '[Daemon] Forced shutdown after timeout' ) ;
322- cleanupWorkspaceDaemonFiles ( workspaceKey ) ;
323- void flushAndCloseSentry ( 1000 ) . finally ( ( ) => {
324- process . exit ( 1 ) ;
346+ void cleanupArtifacts ( ) . finally ( ( ) => {
347+ void flushAndCloseSentry ( 1000 ) . finally ( ( ) => {
348+ process . exit ( 1 ) ;
349+ } ) ;
325350 } ) ;
326351 } , 5000 ) ;
327352 } ;
@@ -384,32 +409,45 @@ async function main(): Promise<void> {
384409 idleCheckTimer . unref ?. ( ) ;
385410 }
386411
412+ server . on ( 'error' , releaseStartupRegistryLock ) ;
413+
387414 server . listen ( socketPath , ( ) => {
388415 log ( 'info' , `[Daemon] Listening on ${ socketPath } ` ) ;
389416
390417 // Write registry entry after successful listen
391- writeDaemonRegistryEntry ( {
392- workspaceKey,
393- workspaceRoot,
394- socketPath,
395- logPath : logPath ?? undefined ,
396- pid : process . pid ,
397- startedAt,
398- enabledWorkflows : daemonWorkflows ,
399- version : String ( version ) ,
400- } ) ;
418+ try {
419+ writeDaemonRegistryEntry (
420+ {
421+ workspaceKey,
422+ workspaceRoot,
423+ socketPath,
424+ logPath : logPath ?? undefined ,
425+ pid : process . pid ,
426+ startedAt,
427+ enabledWorkflows : daemonWorkflows ,
428+ version : String ( version ) ,
429+ } ,
430+ { lock : startupRegistryLock } ,
431+ ) ;
432+ } finally {
433+ releaseStartupRegistryLock ( ) ;
434+ }
401435
402436 writeLine ( `Daemon started (PID: ${ process . pid } )` ) ;
403437 writeLine ( `Workspace: ${ workspaceRoot } ` ) ;
404438 writeLine ( `Socket: ${ socketPath } ` ) ;
405439 writeLine ( `Tools: ${ catalog . tools . length } ` ) ;
406440 recordBootstrapDurationMetric ( 'cli-daemon' , Date . now ( ) - daemonBootstrapStart ) ;
407441
442+ // Filesystem orphan reconciliation and log retention run fire-and-forget after listen so
443+ // a slow sweep cannot delay request serving. Request handlers must not assume orphans
444+ // have been cleaned at startup.
408445 setImmediate ( ( ) => {
409446 void enrichSentryMetadata ( ) . catch ( ( error ) => {
410447 const message = error instanceof Error ? error . message : String ( error ) ;
411448 log ( 'warn' , `[Daemon] Failed to enrich Sentry metadata: ${ message } ` ) ;
412449 } ) ;
450+ void runStartupLifecycleSweep ( ) ;
413451 } ) ;
414452 } ) ;
415453
@@ -421,7 +459,7 @@ async function main(): Promise<void> {
421459 } ;
422460
423461 process . on ( 'exit' , ( ) => {
424- terminateLiveSimulatorLaunchOsLogSessionsSync ( ) ;
462+ terminateOwnedWorkspaceFilesystemArtifactsSync ( ) ;
425463 } ) ;
426464 process . on ( 'SIGTERM' , ( ) => shutdown ( 0 ) ) ;
427465 process . on ( 'SIGINT' , ( ) => shutdown ( 0 ) ) ;
0 commit comments