fix(cli): prevent --version/--help interception in standalone executables with compile-exec-argv (#26083)

## Summary

Fixes https://github.com/oven-sh/bun/issues/26082

- Fixes a bug where standalone executables compiled with
`--compile-exec-argv` would intercept `--version`, `-v`, `--help`, and
`-h` flags before user code could handle them
- CLI applications using libraries like `commander` can now properly
implement their own version and help commands

## Root Cause

When `--compile-exec-argv` is used, `Command.init` was being called with
`.AutoCommand`, which parses ALL arguments (including user arguments).
The `Arguments.parse` function intercepts `--version`/`--help` flags for
`AutoCommand`, preventing them from reaching user code.

## Fix

Temporarily set `bun.argv` to only include the executable name +
embedded exec argv options when calling `Command.init`. This ensures:
1. Bun's embedded options (like `--smol`, `--use-system-ca`) are
properly parsed
2. User arguments (including `--version`/`--help`) are NOT intercepted
by Bun's parser
3. User arguments are properly passed through to user code

## Test plan

- [x] Added tests for `--version`, `-v`, `--help`, and `-h` flags in
`compile-argv.test.ts`
- [x] Verified tests fail with `USE_SYSTEM_BUN=1` (proving the bug
exists)
- [x] Verified tests pass with debug build
- [x] Verified existing compile-argv tests still pass

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

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
robobun
2026-01-14 13:10:53 -08:00
committed by GitHub
parent 5a71ead8a2
commit 6e6896510a
2 changed files with 117 additions and 3 deletions

View File

@@ -690,13 +690,26 @@ pub const Command = struct {
const original_argv_len = bun.argv.len;
var argv_list = std.array_list.Managed([:0]const u8).fromOwnedSlice(bun.default_allocator, bun.argv);
try bun.appendOptionsEnv(graph.compile_exec_argv, &argv_list, bun.default_allocator);
bun.argv = argv_list.items;
// Store the full argv including user arguments
const full_argv = argv_list.items;
const num_exec_argv_options = full_argv.len -| original_argv_len;
// Calculate offset: skip executable name + all exec argv options
offset_for_passthrough = if (bun.argv.len > 1) 1 + (bun.argv.len -| original_argv_len) else 0;
offset_for_passthrough = if (full_argv.len > 1) 1 + num_exec_argv_options else 0;
// Temporarily set bun.argv to only include executable name + exec_argv options.
// This prevents user arguments like --version/--help from being intercepted
// by Bun's argument parser (they should be passed through to user code).
bun.argv = full_argv[0..@min(1 + num_exec_argv_options, full_argv.len)];
// Handle actual options to parse.
break :brk try Command.init(allocator, log, .AutoCommand);
const result = try Command.init(allocator, log, .AutoCommand);
// Restore full argv so passthrough calculation works correctly
bun.argv = full_argv;
break :brk result;
}
context_data = .{

View File

@@ -175,4 +175,105 @@ describe("bundler", () => {
stdout: /SUCCESS: user arguments properly passed with exec argv present/,
},
});
// Test that --version and --help flags are passed through to user code (issue #26082)
// When compile-exec-argv is used, user flags like --version should NOT be intercepted by Bun
itBundled("compile/CompileExecArgvVersionHelpPassthrough", {
compile: {
execArgv: ["--smol"],
},
backend: "cli",
files: {
"/entry.ts": /* js */ `
// Test that --version and --help are passed through to user code, not intercepted by Bun
const args = process.argv.slice(2);
console.log("User args:", JSON.stringify(args));
if (args.includes("--version")) {
console.log("APP_VERSION:1.0.0");
} else if (args.includes("-v")) {
console.log("APP_VERSION:1.0.0");
} else if (args.includes("--help")) {
console.log("APP_HELP:This is my app help");
} else if (args.includes("-h")) {
console.log("APP_HELP:This is my app help");
} else {
console.log("NO_FLAG_MATCHED");
}
`,
},
run: {
args: ["--version"],
stdout: /APP_VERSION:1\.0\.0/,
},
});
// Test with -v short flag
itBundled("compile/CompileExecArgvShortVersionPassthrough", {
compile: {
execArgv: ["--smol"],
},
backend: "cli",
files: {
"/entry.ts": /* js */ `
const args = process.argv.slice(2);
if (args.includes("-v")) {
console.log("APP_VERSION:1.0.0");
} else {
console.log("FAIL: -v not found in args:", args);
process.exit(1);
}
`,
},
run: {
args: ["-v"],
stdout: /APP_VERSION:1\.0\.0/,
},
});
// Test with --help flag
itBundled("compile/CompileExecArgvHelpPassthrough", {
compile: {
execArgv: ["--smol"],
},
backend: "cli",
files: {
"/entry.ts": /* js */ `
const args = process.argv.slice(2);
if (args.includes("--help")) {
console.log("APP_HELP:my custom help");
} else {
console.log("FAIL: --help not found in args:", args);
process.exit(1);
}
`,
},
run: {
args: ["--help"],
stdout: /APP_HELP:my custom help/,
},
});
// Test with -h short flag
itBundled("compile/CompileExecArgvShortHelpPassthrough", {
compile: {
execArgv: ["--smol"],
},
backend: "cli",
files: {
"/entry.ts": /* js */ `
const args = process.argv.slice(2);
if (args.includes("-h")) {
console.log("APP_HELP:my custom help");
} else {
console.log("FAIL: -h not found in args:", args);
process.exit(1);
}
`,
},
run: {
args: ["-h"],
stdout: /APP_HELP:my custom help/,
},
});
});