worker_threads: honor --no-addons in execArgv after value-taking flags - #32362
worker_threads: honor --no-addons in execArgv after value-taking flags#32362robobun wants to merge 9 commits into
Conversation
The hand-rolled execArgv scanner in parse_worker_exec_argv_allow_addons treated every non-'-' token as the first positional and stopped there, without knowing the preceding flag may consume it as a value. So execArgv: ['-r', './preload.js', '--no-addons'] stopped at './preload.js' and left addons enabled. Same for --title, --port, -e, --require, etc. Consult RUN_PARAMS for the previous flag's takes_value (One/Many) and skip the next token when it is a value, matching the Zig clap parse that WebWorker.startVM used.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughBun's clap argument parser is refactored to accept non-static borrowed argument slices. This enables ChangesWorker execArgv --no-addons Flag Parsing Fix
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Hoist the prev_wants_value check above the '-' prefix and '--' terminator checks so a value token is consumed via raw iter.next() regardless of its content, matching StreamingClap. Walk chained-short clusters (-br) so a One/Many short at the end of the cluster pulls the next token.
|
Updated 9:59 PM PT - Jun 15th, 2026
❌ @alii, your commit 86352d8 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 32362That installs a local version of the PR into your bun-32362 --bun |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/node/no-addons.test.ts`:
- Around line 43-46: The Promise in the worker message handling code only
listens for "message" to resolve and "error" to reject, leaving the "exit" event
unhandled. If the worker process exits prematurely without posting a message or
throwing an error, the promise never settles and the test hangs. Add a "exit"
event listener that rejects the promise with an appropriate error message.
Additionally, consider using "once" instead of "on" for all listeners and
implement cleanup logic to remove listeners once the promise settles, ensuring
deterministic test behavior and preventing resource leaks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 897c6cc1-ed58-4b94-b9e8-c5c782025f65
📒 Files selected for processing (2)
src/runtime/jsc_hooks.rstest/js/node/no-addons.test.ts
StreamingClap::chainging stops chaining when takes_value != None, so a OneOptional short terminates the cluster without pulling from the iterator. StreamingClap::parse_next_arg classifies both '--' and '-' as positionals.
Generalize ComptimeClap/Args over the argv lifetime so parse_ex accepts
a borrowed SliceIterator, and rewrite parse_worker_exec_argv_allow_addons
as a direct port of the Zig startVM path: convert the WTF strings to
owned UTF-8, run parse_ex against RUN_PARAMS with
stop_after_positional_at = 1, read args.flag("--no-addons"). Existing
OsIterator callers infer 'static via elision.
Avoids the one-time ConvertedTable::for_params mutex + leak; RUN_TABLE is already baked by comptime_table!.
There was a problem hiding this comment.
All prior feedback is addressed and the final approach (delegating to the real ComptimeClap::parse_with_table(RUN_TABLE, …) instead of a hand-rolled scanner) is the right one — but since this generalises the core clap lifetime bounds and changes how the --no-addons capability gate is computed for workers, it's worth a quick human glance.
Extended reasoning...
Overview
This PR fixes parse_worker_exec_argv_allow_addons so a worker's execArgv: ['-r', './x', '--no-addons'] correctly disables process.dlopen. After four rounds of review the implementation evolved from a hand-rolled token scanner (which had several edge-case parity bugs vs StreamingClap) to simply running the real clap parser against the rodata RUN_TABLE. To enable that, ComptimeClap<Id>/Args<Id> were generalised to ComptimeClap<'a, Id>/Args<'a, Id> so a borrowed SliceIterator can be used; the top-level parse/parse_with_table still return Args<'static, Id>, so existing OsIterator callers are unchanged by elision. A 12-case parameterised test suite covers value-taking flags, chained shorts, --prefixed values, bare -, and the negative case where --no-addons is itself consumed as a value.
Security risks
--no-addons is a capability control — it determines whether process.dlopen (arbitrary native code loading) is permitted in the worker VM. The original bug erred in the unsafe direction (addons stayed enabled when the user asked to disable them); this fix moves in the safe direction and now uses the canonical CLI parser as the single source of truth, so the risk profile improves. I don't see new exposure, but capability-boundary changes warrant a human sanity check.
Level of scrutiny
Moderate. The clap lifetime relaxation is mechanical and borrow-checker-enforced, but it threads through ComptimeClap/Args which back every bun CLI invocation. The jsc_hooks.rs change is a net simplification (~30 lines of bespoke scanning replaced by ~20 lines of delegation to existing, tested infrastructure).
Other factors
All five of my earlier inline findings (chained-short clusters, --prefixed values, bare -, OneOptional chaining, and the cold parse_ex path) are resolved — the last two became moot once the hand-rolled scanner was dropped in favour of the real parser. CodeRabbit's worker-exit hang concern is also addressed. No new issues found in this pass; not auto-approving only because of the security-adjacent surface and the core-infrastructure refactor.
|
CI at d0ef27c: all lanes pass except two unrelated to this diff.
My test file ( |
There was a problem hiding this comment.
LGTM — the switch to the real clap parser via parse_with_table(RUN_TABLE, …) structurally eliminates the parity gaps from the earlier hand-rolled scanner, and the lifetime generalization is type-level only.
Extended reasoning...
Overview
This PR fixes --no-addons detection in a worker's execArgv when it follows a value-taking flag (e.g. ['-r', './preload.js', '--no-addons']). It touches four files: src/clap/comptime.rs and src/clap/lib.rs generalize ComptimeClap<Id>/Args<Id> over an argv lifetime 'a (was hardcoded 'static); src/runtime/jsc_hooks.rs rewrites parse_worker_exec_argv_allow_addons to delegate to the canonical clap parser via ComptimeClap::parse_with_table(RUN_TABLE, …) instead of a hand-rolled scanner; and test/js/node/no-addons.test.ts adds an 11-case parameterized matrix.
I reviewed earlier revisions of this PR that kept a hand-rolled flag_consumes_next_token scanner and flagged several StreamingClap-parity gaps (chained shorts, --prefixed values, bare -, OneOptional). Those were each addressed, and then the final revision dropped the hand-rolled scanner entirely in favor of the real parser — which structurally removes that whole class of divergence. The last nit (use rodata RUN_TABLE via parse_with_table instead of the cold parse_ex/for_params path) was also applied in d0ef27c.
Security risks
--no-addons gates process.dlopen, so this is security-adjacent. The fix moves strictly in the safe direction (correctly disabling native addons when the user asked for it) and now uses exactly the same parser and param table the main CLI uses, so there is no second source of truth to drift. The two negative test cases (['-r', '--no-addons'] and ['-', '--no-addons']) confirm the parser doesn't over-disable either. No new attack surface.
Level of scrutiny
Moderate. The clap changes touch core CLI infrastructure, but they are a pure type-level relaxation: &'static [u8] → &'a [u8] on stored argv slices, with the public parse/parse_with_table entry points still returning Args<'static, Id> explicitly — so every existing OsIterator caller is byte-identical and compiler-verified. The jsc_hooks.rs change is a net reduction in custom logic (~25 lines of bespoke scanning replaced by ~20 lines that build a SliceIterator and call the canonical parser).
Other factors
All prior inline comments (mine and CodeRabbit's) are resolved. The bug-hunting system found nothing in the current revision. CI is green on every lane that ran the test file, including ASAN; the two red lanes are a darwin-26 VM-image issue and a known flaky PTY test on darwin-14-x64, both documented as reproducing on unrelated PRs. No CODEOWNERS cover these paths.
What does this PR do?
WebWorker.startVMparses the worker'sexecArgvto pick up--no-addons. The Zig implementation runsbun.clap.parseExagainst the full RunCommand param table withstop_after_positional_at = 1, so value-taking flags like-r <preload>,--title <name>,--port <n>and-e <code>correctly consume their value token before the first positional ends parsing.The Rust port's
parse_worker_exec_argv_allow_addonsreplaced this with a hand-rolled scanner that breaks at the first token not prefixed with-, without knowing the previous flag may consume that token as its value. ForexecArgv: ['-r', './preload.js', '--no-addons']it stops at'./preload.js'and never sees--no-addons, sotransform_options.allow_addonsstaystrueandprocess.dlopenis not disabled.Repro
Before: prints
ERR_DLOPEN_FAILED(addons enabled, dlopen attempted).After: prints
ERR_DLOPEN_DISABLED.Same divergence with
--title,--port,-e,--require, chained shorts (-br), and--prefixed values.Fix
Generalize
bun_clap::ComptimeClap/Argsover the argv lifetime soparse_exaccepts a borrowedSliceIterator(the prior'staticbound was why the Rust port hand-rolled the scan).parse_worker_exec_argv_allow_addonsis now a direct port of the ZigstartVMpath: convert the WTF strings to owned UTF-8, runparse_exagainstRUN_PARAMSwithstop_after_positional_at = 1, readargs.flag("--no-addons"). ExistingOsIteratorcallers infer'staticvia elision, so no call sites change.How did you verify your code works?
New parameterised test in
test/js/node/no-addons.test.tscovering-r,--require,--title,--port,-e, chained shorts (-br),--prefixed values (-r --), bare-, and the case where--no-addonsis itself consumed as a value. Nine of the twelve cases fail on main and all pass with the fix.