Compare commits

...

2 Commits

Author SHA1 Message Date
Peter Steinberger
02058c37fa
fix: handle Bun daemon argv duplication (#112) (thanks @dedene) 2026-03-28 20:58:47 +00:00
Peter Dedene
20bd05bfda
fix(daemon): skip Bun virtual entry path in detached child spawn
In Bun compiled binaries, process.argv[1] is a virtual /$bunfs/...
path that Bun auto-injects into every spawned child process. The
daemon launcher was including this path explicitly in the spawn args,
causing it to appear twice in the child's argv. The child then parsed
the duplicate path as the CLI command instead of "daemon", silently
failing to start.

Detect the /$bunfs/ prefix and omit the entry from spawn args, letting
Bun handle it automatically.
2026-03-28 20:56:12 +00:00
3 changed files with 59 additions and 2 deletions

View File

@ -3,6 +3,7 @@
## [Unreleased]
### CLI
- Skip Bun compiled `/$bunfs/...` argv entries when detached daemon restarts spawn child processes, so compiled binaries do not duplicate the virtual entry path and lose the `daemon` command. (PR #112, thanks @dedene)
- Keep `mcporter call --output json` parseable by emitting valid JSON even when the command falls back to raw output. (PR #128, thanks @armanddp)
- Ignore static `Authorization` headers once OAuth is active so imported editor configs cannot override fresh OAuth tokens. (PR #123, thanks @ahonn)
- Preserve full JSON/error payloads when `data` is just one field instead of collapsing the response to `data` alone. (PR #106, thanks @AielloChan)

View File

@ -15,7 +15,7 @@ export function launchDaemonDetached(options: DaemonLaunchOptions): void {
const configArgs = options.configExplicit ? ['--config', options.configPath] : [];
const args = [
...process.execArgv,
cliEntry,
...(cliEntry ? [cliEntry] : []),
...configArgs,
...(options.rootDir ? ['--root', options.rootDir] : []),
'daemon',
@ -36,10 +36,16 @@ export function launchDaemonDetached(options: DaemonLaunchOptions): void {
child.unref();
}
function resolveCliEntry(): string {
function resolveCliEntry(): string | undefined {
const entry = process.argv[1];
if (!entry) {
throw new Error('Unable to resolve mcporter entry script.');
}
// In Bun compiled binaries, argv[1] is a virtual /$bunfs/... path that Bun
// auto-injects into every spawned child. Including it explicitly would
// duplicate it and break CLI argument parsing in the child process.
if (entry.startsWith('/$bunfs/')) {
return undefined;
}
return path.resolve(entry);
}

View File

@ -0,0 +1,50 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const spawnMock = vi.fn(() => ({ unref: vi.fn() }));
vi.mock('node:child_process', () => ({
spawn: spawnMock,
}));
const originalArgv = [...process.argv];
const originalExecArgv = [...process.execArgv];
describe('launchDaemonDetached', () => {
beforeEach(() => {
spawnMock.mockClear();
process.argv = ['/tmp/mcporter', '/$bunfs/root/mcporter.js'];
process.execArgv = ['--smol'];
});
afterEach(() => {
process.argv = [...originalArgv];
process.execArgv = [...originalExecArgv];
});
it('omits Bun virtual entry paths from detached child args', async () => {
const { launchDaemonDetached } = await import('../src/daemon/launch.js');
launchDaemonDetached({
configPath: '/tmp/mcporter.json',
configExplicit: true,
rootDir: '/repo',
socketPath: '/tmp/mcporter.sock',
metadataPath: '/tmp/mcporter.meta.json',
extraArgs: ['--log'],
});
expect(spawnMock).toHaveBeenCalledWith(
process.execPath,
['--smol', '--config', '/tmp/mcporter.json', '--root', '/repo', 'daemon', 'start', '--foreground', '--log'],
expect.objectContaining({
detached: true,
stdio: 'ignore',
env: expect.objectContaining({
MCPORTER_DAEMON_CHILD: '1',
MCPORTER_DAEMON_SOCKET: '/tmp/mcporter.sock',
MCPORTER_DAEMON_METADATA: '/tmp/mcporter.meta.json',
}),
})
);
});
});