You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Official commander docs example compiles successfully, but the generated binary ignores CLI arguments at runtime #187
I copied the official Perry commander example and compiled it successfully, but the resulting native binary ignores the CLI at runtime. --help and serve --port 3000 both exit with status 0 and print nothing to stdout or stderr.
What you expected
I expected the compiled binary to behave like the commander example normally does: --help should print help text, and serve --port 3000 should run the action and print Starting server on port 3000.
Minimal reproduction
This is the exact sample from the Perry docs commander section.
import{Command}from"commander";constprogram=newCommand();program.name("my-cli").version("1.0.0").description("My CLI tool");program.command("serve").option("-p, --port <number>","Port number").option("--verbose","Verbose output").action((options)=>{console.log(`Starting server on port ${options.port}`);});program.parse(process.argv);
Perry Doctor
Environment Checks
──────────────────
[OK] perry version: 0.5.178
[OK] update status: no cached info (run `perry update --check-only`)
[OK] system linker (cc): Apple clang version 17.0.0 (clang-1700.6.4.2)
[WARN] runtime library: not found - run: cargo build --release -p perry-runtime
[OK] project config (perry.toml): found
All critical checks passed with some warnings.
Anything else
I copied this from the Perry docs commander example, so this currently looks like a docs-backed reproduction rather than custom project code.
A manual process.argv.slice(2) parser works as a workaround in the same environment.
I also observed that process.argv itself is populated in Perry, so the failure seems specific to commander behavior under Perry rather than missing argv data.
new Command() allocated a placeholder.Expr::New { class_name: "Command" } fell into lower_call.rs::lower_new's empty-class fallback, producing an empty js_object_alloc(0,0) ObjectHeader instead of calling js_commander_new(). So the receiver every fluent method dispatched against was junk.
No method dispatch entries.crates/perry-codegen/src/lower_call.rs::NATIVE_MODULE_TABLE had zero rows for module: "commander", so every program.command(...) / .option(...) / .action(...) / .parse(...) call fell through the unknown-method sentinel and silently returned double 0.0. The .action() callback in particular was taken as _callback: i64 and immediately discarded — there was even a comment in the runtime saying "Action callbacks are stored but not automatically invoked".
HIR fluent chain. Even fixing the above, only the innermost program.name(...) got recognized as a NativeMethodCall — the chained .version(...).description(...) were generic Call+PropertyGet because the receiver was no longer a LocalGet of a registered native instance.
Fixed across three commits on main:
6f109b12 (v0.5.249) — HIR fluent-chain detector extended to commander so chained methods keep dispatching as NativeMethodCalls.
22ac6595 (v0.5.250) — crates/perry-stdlib/src/commander.rs rewrite: subcommand registration, action callback storage, real parse_and_dispatch, built-in --help/-h/--version/-V, options-object construction via js_object_alloc + js_object_set_field_by_name, action invocation via js_closure_call1, GC root scanner pinning stored callbacks across collections. Plus 9 NATIVE_MODULE_TABLE rows + a "Command" branch in lower_builtin_new calling js_commander_new().
120208a6 (v0.5.270) — bonus: same bug shape applied to Redis (ioredis) plus 9 dispatch-table-symbol fixes. Sibling work from this session also closed the same shape on EventEmitter, LRUCache, WebSocketServer, Decimal (decimal.js), AsyncLocalStorage, pg Client/Pool, and MongoClient.
Verified end-to-end against your exact repro (commander@14.0.0 from npm):
$ ./app --help
My CLI tool
Usage: my-cli [options] [command]
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
serve
$ ./app --version
1.0.0
$ ./app serve --port 3000
Starting server on port 3000
$ ./app serve --port 3000 --verbose
Starting server on port 3000
To get the fix today: build from main. Otherwise it'll be in the next release (cutting later today, within a few hours from this comment). Thanks for the clean repro — it surfaced a whole family of latent stub-grade native-class bugs that have now all been swept.
Activity
proggeramlug commentedon Apr 25, 2026
I'm taking a detailed look, getting back to you soon!
fix(stdlib): #187 commander runtime + codegen — actually invoke .acti…
fix(codegen): #187 follow-up — same new T() bug on EE/LRU/WSS (v0.5.252)
fix(stdlib): #187 follow-up — land decimal.js code (v0.5.259)
fix(codegen): #187 follow-up — AsyncLocalStorage end-to-end (v0.5.261)
proggeramlug commentedon Apr 25, 2026
Reproduced and fixed.
Root cause was two layers deep:
new Command()allocated a placeholder.Expr::New { class_name: "Command" }fell intolower_call.rs::lower_new's empty-class fallback, producing an emptyjs_object_alloc(0,0)ObjectHeader instead of callingjs_commander_new(). So the receiver every fluent method dispatched against was junk.No method dispatch entries.
crates/perry-codegen/src/lower_call.rs::NATIVE_MODULE_TABLEhad zero rows formodule: "commander", so everyprogram.command(...)/.option(...)/.action(...)/.parse(...)call fell through the unknown-method sentinel and silently returneddouble 0.0. The.action()callback in particular was taken as_callback: i64and immediately discarded — there was even a comment in the runtime saying "Action callbacks are stored but not automatically invoked".HIR fluent chain. Even fixing the above, only the innermost
program.name(...)got recognized as aNativeMethodCall— the chained.version(...).description(...)were generic Call+PropertyGet because the receiver was no longer aLocalGetof a registered native instance.Fixed across three commits on
main:6f109b12(v0.5.249) — HIR fluent-chain detector extended tocommanderso chained methods keep dispatching asNativeMethodCalls.22ac6595(v0.5.250) —crates/perry-stdlib/src/commander.rsrewrite: subcommand registration, action callback storage, realparse_and_dispatch, built-in--help/-h/--version/-V, options-object construction viajs_object_alloc+js_object_set_field_by_name, action invocation viajs_closure_call1, GC root scanner pinning stored callbacks across collections. Plus 9NATIVE_MODULE_TABLErows + a"Command"branch inlower_builtin_newcallingjs_commander_new().120208a6(v0.5.270) — bonus: same bug shape applied toRedis(ioredis) plus 9 dispatch-table-symbol fixes. Sibling work from this session also closed the same shape onEventEmitter,LRUCache,WebSocketServer,Decimal(decimal.js),AsyncLocalStorage, pgClient/Pool, andMongoClient.Verified end-to-end against your exact repro (
commander@14.0.0from npm):To get the fix today: build from
main. Otherwise it'll be in the next release (cutting later today, within a few hours from this comment). Thanks for the clean repro — it surfaced a whole family of latent stub-grade native-class bugs that have now all been swept.fix: #187 follow-up — SSO + property-read NaN bug (v0.5.279)
mktbsh commentedon Apr 26, 2026
The issue has been successfully resolved. I truly appreciate your quick support in fixing this.
If you ever decide to set up GitHub Sponsors, I would love to become a sponsor.
proggeramlug commentedon Apr 26, 2026
@mktbsh Waiting for approval, will tag you here once it is through :)
Thank you so much, it means a lot to me!
proggeramlug commentedon Apr 26, 2026
@mktbsh if you want to, Ko-Fi is up already :) https://ko-fi.com/perryts