Package
@bomb.sh/tab
Package Version
0.0.17
Node.js Version
v24.15.0
Operating System
macOS
Describe the bug
Environment
- Shell: zsh (macOS)
- Runtime: bun compiled binary (
bun build --compile)
- Also tested with: tsx (Node.js)
Problem
All three framework adapters (commander, cac, citty) construct an exec path string x at module load time:
const execPath = process.execPath;
const processArgs = process.argv.slice(1);
const x = `${quotedExecPath} ${quotedProcessExecArgs.join(' ')} ${quotedProcessArgs[0]}`;
This x is baked into generated completion scripts as the command to invoke for completions:
requestComp="/path/to/node --import tsx /path/to/script.ts complete -- ${quoted_args[*]}"
This breaks when the CLI is compiled to a binary with bun build --compile, because bun injects a virtual filesystem path into process.argv[1] (/$bunfs/root/binary-name), producing an invalid command:
requestComp="/path/to/my-cli /$bunfs/root/my-cli complete -- ..."
Root Cause
The x construction assumes Node's process.argv structure where argv[0] is the interpreter and argv[1] is the script path. This assumption doesn't hold for compiled runtimes (bun, deno compile, pkg, etc.) where argv[1] is already a user argument or an internal virtual path.
How Shell Completion Triggering Works (zsh)
In zsh, completion scripts use compdef to bind a completion function to a command name:
compdef _my-cli my-cli # bind _my-cli function to "my-cli" command
When the user types my-cli <Tab>, zsh checks:
- Is
my-cli a recognized command? (in PATH, alias, or shell function)
- If yes → look up
compdef binding → call _my-cli
- If no → fall back to default completion (file paths, etc.)
The requestComp variable inside _my-cli is only executed after the function is triggered. It doesn't affect whether completion is triggered in the first place.
This means: as long as the command name is recognized by the shell, using the program name in requestComp is sufficient — the shell resolves it via PATH, alias, or function. This is how mature implementations work (Go's cobra, Rust's clap, Python's argcomplete).
Why x Has No Practical Benefit
Given the zsh completion trigger mechanism above, the auto-constructed x provides no value in any real scenario:
| Scenario |
Completion triggers? |
Program name sufficient? |
| Global install / binary in PATH |
Yes (my-cli recognized) |
Yes |
Via package manager delegation (pnpm my-cli) |
Yes (pnpm in PATH) |
Yes (pnpm resolves local deps) |
| Binary not in PATH, with shell function |
Yes (function recognized) |
Yes (function resolves to binary) |
| Binary not in PATH, no alias/function |
No (command not recognized) |
Nothing works |
| Development via tsx, no alias/function |
No (command not recognized) |
Nothing works |
In every scenario where completion can trigger, the program name works. In scenarios where it can't trigger, no x construction can help.
Proposed Fix
Replace the runtime-specific x construction with the program name in all adapters:
// Before (commander adapter)
t.setup(programName, x, shell);
// After
t.setup(programName, programName, shell);
This:
- Eliminates runtime-specific bugs (bun, deno, pkg, etc.)
- Removes ~15 lines of fragile code per adapter
- Aligns with industry standards (cobra, clap, argcomplete)
- Works with the package manager delegation feature (
pnpm my-cli)
For development testing without PATH installation, users can create a shell function:
my-cli() { ./my-cli "$@"; }
source <(my-cli complete zsh)
my-cli greet <TAB> # completion works
Affected Files
src/commander.ts (lines 9-18)
src/cac.ts (lines 10-16)
src/citty.ts (lines 22-27)
To Reproduce
1.install bun
2.bun build examples/demo.commander-async.ts --compile --target=bun-darwin-arm64 --outfile my-cli
3.source <(./my-cli complete zsh)
4.complete not work
Expected behavior
bun binary version also works
Additional Information
No response
AI assistance disclosure
Package
@bomb.sh/tab
Package Version
0.0.17
Node.js Version
v24.15.0
Operating System
macOS
Describe the bug
Environment
bun build --compile)Problem
All three framework adapters (commander, cac, citty) construct an exec path string
xat module load time:This
xis baked into generated completion scripts as the command to invoke for completions:requestComp="/path/to/node --import tsx /path/to/script.ts complete -- ${quoted_args[*]}"This breaks when the CLI is compiled to a binary with
bun build --compile, because bun injects a virtual filesystem path intoprocess.argv[1](/$bunfs/root/binary-name), producing an invalid command:requestComp="/path/to/my-cli /$bunfs/root/my-cli complete -- ..."Root Cause
The
xconstruction assumes Node'sprocess.argvstructure whereargv[0]is the interpreter andargv[1]is the script path. This assumption doesn't hold for compiled runtimes (bun, deno compile, pkg, etc.) whereargv[1]is already a user argument or an internal virtual path.How Shell Completion Triggering Works (zsh)
In zsh, completion scripts use
compdefto bind a completion function to a command name:compdef _my-cli my-cli # bind _my-cli function to "my-cli" commandWhen the user types
my-cli <Tab>, zsh checks:my-clia recognized command? (in PATH, alias, or shell function)compdefbinding → call_my-cliThe
requestCompvariable inside_my-cliis only executed after the function is triggered. It doesn't affect whether completion is triggered in the first place.This means: as long as the command name is recognized by the shell, using the program name in
requestCompis sufficient — the shell resolves it via PATH, alias, or function. This is how mature implementations work (Go's cobra, Rust's clap, Python's argcomplete).Why
xHas No Practical BenefitGiven the zsh completion trigger mechanism above, the auto-constructed
xprovides no value in any real scenario:my-clirecognized)pnpm my-cli)pnpmin PATH)In every scenario where completion can trigger, the program name works. In scenarios where it can't trigger, no
xconstruction can help.Proposed Fix
Replace the runtime-specific
xconstruction with the program name in all adapters:This:
pnpm my-cli)For development testing without PATH installation, users can create a shell function:
Affected Files
src/commander.ts(lines 9-18)src/cac.ts(lines 10-16)src/citty.ts(lines 22-27)To Reproduce
1.install bun
2.bun build examples/demo.commander-async.ts --compile --target=bun-darwin-arm64 --outfile my-cli
3.source <(./my-cli complete zsh)
4.complete not work
Expected behavior
bun binary version also works
Additional Information
No response
AI assistance disclosure