Files
bun.sh/test/regression/issue/22157.test.ts
robobun 0315c97e7b Fix argv handling for standalone binaries - remove extra executable name (#22157) (#22169)
## Summary

Fixes an issue where compiled standalone binaries included an extra
executable name argument in `process.argv`, breaking code that uses
`node:util.parseArgs()` with `process.argv.slice(2)`.

## Problem

When running a compiled binary, `process.argv` incorrectly included the
executable name as a third argument:

```bash
./my-app
# process.argv = ["bun", "/$bunfs/root/my-app", "./my-app"]  # BUG
```

This caused `parseArgs()` to fail with "Unexpected argument" errors,
breaking previously valid code.

## Solution

Fixed the `offset_for_passthrough` calculation in `cli.zig` to always
skip the executable name for standalone binaries, ensuring
`process.argv` only contains the runtime name and script path:

```bash  
./my-app
# process.argv = ["bun", "/$bunfs/root/my-app"]  # FIXED
```

## Test plan

- [x] Added regression test in `test/regression/issue/22157.test.ts`
- [x] Verified existing exec-argv functionality still works correctly  
- [x] Manual testing confirms the fix resolves the parseArgs issue

Fixes #22157

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Michael H <git@riskymh.dev>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-08-27 15:31:28 -07:00

101 lines
2.8 KiB
TypeScript

import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
// Regression test for https://github.com/oven-sh/bun/issues/22157
// Compiled binaries were including executable name in process.argv
test("issue 22157: compiled binary should not include executable name in process.argv", async () => {
const dir = tempDirWithFiles("22157-basic", {
"index.js": /* js */ `
import { parseArgs } from "node:util"
console.log(JSON.stringify(process.argv));
// This should work - no extra executable name should cause parseArgs to throw
parseArgs({
args: process.argv.slice(2),
});
console.log("SUCCESS");
`,
});
// Compile the binary
await using compileProc = Bun.spawn({
cmd: [bunExe(), "build", "--compile", "--outfile=test-binary", "./index.js"],
cwd: dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await compileProc.exited;
// Run the compiled binary - should not throw
await using runProc = Bun.spawn({
cmd: ["./test-binary"],
cwd: dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, exitCode] = await Promise.all([runProc.stdout.text(), runProc.exited]);
expect(exitCode).toBe(0);
expect(stdout).toContain("SUCCESS");
// Verify process.argv structure
const argvMatch = stdout.match(/\[.*?\]/);
expect(argvMatch).toBeTruthy();
const processArgv = JSON.parse(argvMatch![0]);
expect(processArgv).toHaveLength(2);
expect(processArgv[0]).toBe("bun");
// Windows uses "B:/~BUN/root/", Unix uses "/$bunfs/root/"
expect(processArgv[1]).toMatch(/(\$bunfs|~BUN).*root/);
});
test("issue 22157: compiled binary with user args should pass them correctly", async () => {
const dir = tempDirWithFiles("22157-args", {
"index.js": /* js */ `
console.log(JSON.stringify(process.argv));
// Expect: ["bun", "/$bunfs/root/..." or "B:/~BUN/root/...", "arg1", "arg2"]
if (process.argv.length !== 4) {
console.error("Expected 4 argv items, got", process.argv.length);
process.exit(1);
}
if (process.argv[2] !== "arg1" || process.argv[3] !== "arg2") {
console.error("User args not correct");
process.exit(1);
}
console.log("SUCCESS");
`,
});
await using compileProc = Bun.spawn({
cmd: [bunExe(), "build", "--compile", "--outfile=test-binary", "./index.js"],
cwd: dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
await compileProc.exited;
await using runProc = Bun.spawn({
cmd: ["./test-binary", "arg1", "arg2"],
cwd: dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, exitCode] = await Promise.all([runProc.stdout.text(), runProc.exited]);
expect(exitCode).toBe(0);
expect(stdout).toContain("SUCCESS");
});