Skip to content

Commit c2df5b2

Browse files
IM.codesclaude
andcommitted
fix: 'imcodes start' delegates to launchd/systemd (prevents duplicates)
Running 'imcodes start' from terminal started an inline daemon process. If launchd was also managing a daemon, two processes competed for the same WebSocket connection, causing infinite reconnect loops (close 1001 "replaced" every 2 seconds). Now 'imcodes start' delegates to launchctl/systemd by default. '--foreground' flag runs inline (used by plist/unit files only). Updated plist to pass --foreground. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 7aebddb commit c2df5b2

1 file changed

Lines changed: 39 additions & 6 deletions

File tree

src/index.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,45 @@ const program = new Command()
2020

2121
program
2222
.command('start')
23-
.description('Start the daemon (connect to CF server, restore sessions)')
24-
.action(async () => {
25-
await startup();
26-
logger.info('Daemon running. Press Ctrl+C to stop.');
27-
// Keep process alive — signal handlers in lifecycle.ts handle exit
28-
await new Promise(() => {});
23+
.description('Start the daemon via system service (launchd/systemd)')
24+
.option('--foreground', 'Run in foreground (for service managers, not manual use)')
25+
.action(async (opts: { foreground?: boolean }) => {
26+
if (opts.foreground) {
27+
// Called by launchd/systemd plist/unit — run inline
28+
await startup();
29+
logger.info('Daemon running. Press Ctrl+C to stop.');
30+
await new Promise(() => {});
31+
return;
32+
}
33+
34+
// Interactive: delegate to system service to avoid duplicate processes
35+
const platform = process.platform;
36+
if (platform === 'darwin') {
37+
const plist = resolve(homedir(), 'Library/LaunchAgents/imcodes.daemon.plist');
38+
if (!existsSync(plist)) {
39+
console.error(`No service installed. Run 'imcodes service install' first, or use 'imcodes start --foreground'.`);
40+
process.exit(1);
41+
}
42+
try { execSync(`launchctl unload "${plist}" 2>/dev/null`, { stdio: 'pipe' }); } catch { /* may not be loaded */ }
43+
execSync(`launchctl load "${plist}"`, { stdio: 'inherit' });
44+
console.log('Daemon started via launchctl.');
45+
} else if (platform === 'linux') {
46+
const userService = existsSync(resolve(homedir(), '.config/systemd/user/imcodes.service'));
47+
if (userService) {
48+
execSync('systemctl --user start imcodes', { stdio: 'inherit' });
49+
} else {
50+
try { execSync('sudo systemctl start imcodes', { stdio: 'inherit' }); } catch {
51+
console.error(`No service installed. Run 'imcodes service install' first, or use 'imcodes start --foreground'.`);
52+
process.exit(1);
53+
}
54+
}
55+
console.log('Daemon started via systemd.');
56+
} else {
57+
// Fallback: run inline
58+
await startup();
59+
logger.info('Daemon running. Press Ctrl+C to stop.');
60+
await new Promise(() => {});
61+
}
2962
});
3063

3164
program

0 commit comments

Comments
 (0)