Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions packages/cli/src/commands/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3222,25 +3222,60 @@ describe('buildDetachedRunCmd', () => {
expect(cmd).toContain('--conversation-id');
});

it('binary mode: uses [execPath] only (no duplicated entry arg), slices argv(1)', () => {
// A Bun single-file executable's argv is NOT [binary, ...userArgs]. Bun
// injects a virtual entry path at argv[1] and reports argv[0] as 'bun':
// ['bun', '/$bunfs/root/archon', 'workflow', 'run', ...]
// Verified against a real `bun build --compile` artifact. The previous
// fixture modelled a compiled argv with no argv[1] at all, which is why
// #2248 (detached child dies with `Unknown command: B:/~BUN/root/...`)
// shipped green.
it('binary mode: uses [execPath] only (no duplicated entry arg), drops the Bun SFE virtual argv[1]', () => {
const cmd = buildDetachedRunCmd(
true,
'/usr/local/bin/archon',
['/usr/local/bin/archon', 'workflow', 'run', 'assist', 'hello', '--detach', '--json'],
['bun', '/$bunfs/root/archon', 'workflow', 'run', 'assist', 'hello', '--detach', '--json'],
'/abs/cwd',
['--branch', 'assist-123']
);

expect(cmd[0]).toBe('/usr/local/bin/archon');
// The binary path must appear exactly once — never duplicated as argv[1].
expect(cmd.filter(arg => arg === '/usr/local/bin/archon')).toHaveLength(1);
// The virtual entry path must never reach the child: cli.ts parses
// process.argv.slice(2), so a leaked argv[1] becomes the child's command.
expect(cmd.some(arg => arg.includes('$bunfs'))).toBe(false);
expect(cmd[1]).toBe('workflow');
expect(cmd).not.toContain('--detach');
expect(cmd).not.toContain('--json');
const cwdIdx = cmd.indexOf('--cwd');
expect(cmd[cwdIdx + 1]).toBe('/abs/cwd');
expect(cmd.slice(cwdIdx + 2)).toEqual(['--branch', 'assist-123']);
});

it('binary mode: drops the Windows Bun SFE virtual argv[1] (#2248 repro)', () => {
const cmd = buildDetachedRunCmd(
true,
'C:\\Users\\dev\\archon.exe',
[
'bun',
'B:/~BUN/root/archon-windows-x64.exe',
'workflow',
'run',
'assist',
'hello',
'--detach',
'--json',
],
'C:\\checkout',
['--branch', 'assist-123']
);

expect(cmd[0]).toBe('C:\\Users\\dev\\archon.exe');
// The exact token that appeared as `Unknown command: ...` in the report.
expect(cmd).not.toContain('B:/~BUN/root/archon-windows-x64.exe');
expect(cmd[1]).toBe('workflow');
expect(cmd[2]).toBe('run');
});
});

describe('workflowResumeCommand', () => {
Expand Down
16 changes: 11 additions & 5 deletions packages/cli/src/commands/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,18 @@ export function buildDetachedRunCmd(
cwd: string,
extraArgs: string[]
): string[] {
// In a compiled binary, execPath IS the archon binary and there is no
// entry-script argv[1]; in dev, execPath is bun and argv[1] is the cli entry.
// Only the command prefix differs between modes: in a compiled binary
// execPath IS the archon binary and re-invoking it needs no entry script; in
// dev, execPath is bun and argv[1] is the cli entry that bun must be handed.
const baseCmd = isBinary ? [execPath] : [execPath, argv[1]];
const userArgs = (isBinary ? argv.slice(1) : argv.slice(2)).filter(
arg => arg !== '--detach' && arg !== '--json'
);
// User args always start at argv[2] in BOTH modes. A Bun single-file
// executable does have an argv[1] — the virtual entry path
// (`/$bunfs/root/<name>`, `B:/~BUN/root/<name>.exe` on Windows) — so slicing
// from 1 in binary mode leaked that path in as the child's first token and
// the child died with `Unknown command: B:/~BUN/root/archon-...exe` (#2248).
// cli.ts's own parser reads `process.argv.slice(2)` unconditionally, which is
// the contract this must match.
const userArgs = argv.slice(2).filter(arg => arg !== '--detach' && arg !== '--json');
// --cwd is appended last (parseArgs last-wins) so the child resolves the same
// absolute working dir regardless of any relative --cwd the caller passed.
return [...baseCmd, ...userArgs, '--cwd', cwd, ...extraArgs];
Expand Down
Loading