Skip to content

shell(mkdir): accept --verbose instead of the misspelling --vebose - #39221

Open
robobun wants to merge 3 commits into
mainfrom
farm/4762795d/shell-mkdir-verbose-long-flag
Open

shell(mkdir): accept --verbose instead of the misspelling --vebose#39221
robobun wants to merge 3 commits into
mainfrom
farm/4762795d/shell-mkdir-verbose-long-flag

Conversation

@robobun

@robobun robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • In the Bun shell, mkdir --verbose dir exits 1 with mkdir: illegal option -- verbose and creates nothing, while mkdir -v dir and mkdir --parents dir work.
  • parse_long in src/runtime/shell/builtin/mkdir.rs:438 matches the misspelling --vebose instead of --verbose, so the real flag falls through to short-flag parsing of -verbose, which rejects the leading -.
  • The misspelling dates back to the first version of the builtin; the Opts doc comment right above it says -v, --verbose. The comment calling the typo intentional was added when the file was ported and only reflects keeping the port faithful: --vebose was never documented, and GNU and BSD mkdir reject it.
  • Reproduces on Linux and Windows with bun 1.4.0 (mkdir is a builtin on every platform).

Fix

  • parse_long matches --verbose and sets verbose, the same thing -v does. --vebose is no longer accepted and is reported like any other unknown option.
  • Correct because --verbose is the long form of -v both in the builtin's own Opts documentation and in the coreutils mkdir these builtins mirror. No other long flag under src/runtime/shell/builtin/ is misspelled (checked every -- literal there), so this is the only site.
  • Verified with the new test/js/bun/shell/commands/mkdir.test.ts: 8 of its 15 cases fail on bun 1.4.0 (every --verbose case plus the --vebose rejection) and all 15 pass with this change, on Linux and on Windows. test/js/bun/shell/exec.test.ts, which pins mkdir --help as an illegal option, still passes.
  • The test covers --verbose alone, combined with -p / --parents in both orders, the silent output without a verbose flag, --verbose --parents on an existing directory (nothing created, nothing printed), the EEXIST failure (nothing printed, exit 1), and --vebose being rejected with nothing created.
  • Two assertions deliberately pin less than the whole stderr line because the rest of it belongs to other in-flight changes: the --vebose case pins the mkdir: illegal option -- prefix (the option text echoed after it is being corrected separately), and the EEXIST cases accept the path either resolved or as written (shell: report the operand as written in cat, touch and mkdir errors #39189 switches mkdir's errors to the operand as written) and either wording of EEXIST (the macOS table in bun_core's coreutils error map says File or folder exists, which is being fixed separately). Empty stdout and the exit code, the part this PR is responsible for, stay exact.
  • Other open PRs that touch this file: shell: report --name=value spellings of unsupported builtin options as unsupported #39223 (mkdir --mode / -m messages) edits the same impl FlagParser a few lines away and, like shell: fail an empty operand with ENOENT instead of acting on the cwd #38002 and shell(mkdir, touch): report operands longer than the path buffers instead of aborting #38379, also creates test/js/bun/shell/commands/mkdir.test.ts. The mkdir.rs hunks do not overlap; the test files share the same helper and wrapper, so whichever lands later keeps both blocks.

Background

  • The mkdir, touch, cat and cp builtins parse their options through parse_flags in src/runtime/shell/interpreter.rs. For an argument starting with -- it first offers the whole word to the builtin's FlagParser::parse_long; if that returns None, the word is parsed again as a cluster of short flags (-verbose becomes -, v, e, ...), and the first unknown letter produces the illegal option error. A missing long-flag arm therefore shows up as an "illegal option", not as a dedicated message.
  • A verbose mkdir prints the absolute path of every directory it created, one per line (with -p, the intermediate directories too). That output code is shared by -v and --verbose; this change only affects which spellings turn it on.
  • Builtin error lines are <builtin>: <path>: <message>, where the message comes from Bun's own errno-to-coreutils-text table (coreutils_error_map in src/bun_core/result.rs), not from the host's strerror.

[review] gate passed · iteration 0 · 2 files touched

fails on main (without fix)
ASAN without fix: 8 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/shell/commands/mkdir.test.ts
bun test v1.4.0 (eff1f3e4b)

test/js/bun/shell/commands/mkdir.test.ts:
27 |       ["--verbose -p", "a/b", ["a", "a/b"]],
28 |     ])("mkdir %s %s prints %j", async (flags, operand, created) => {
29 |       using dir = tempDir("mkdir-verbose", {});
30 |       const cwd = String(dir);
31 | 
32 |       expect(await mkdir(cwd, flags, operand)).toEqual({
                                                    ^
error: expect(received).toEqual(expected)

  {
-   "exitCode": 0,
-   "stderr": "",
-   "stdout": 
- "/tmp/mkdir-verbose_4vOQKx/dir
+   "exitCode": 1,
+   "stderr": 
+ "mkdir: illegal option -- verbose
  "
  ,
+   "stdout": "",
  }

- Expected  - 4
+ Received  + 4

      at <anonymous> (/workspace/bun/test/js/bun/shell/commands/mkdir.test.ts:32:48)
(fail) bunshell mkdir > verbose > mkdir --verbose dir prints ["dir"] [25.85ms]
27 |       ["--verbose -p", "a/b", ["a", "a/b"]],
28 |     ])("mkdir %s %s prints %j", async (flags, operand, created) => {
29 |       using dir = tempDir("mkdir-verbose", {}
... (truncated)

release without fix: 8 FAILED
bun test v1.4.0-canary.1 (eabb96de7)

test/js/bun/shell/commands/mkdir.test.ts:
27 |       ["--verbose -p", "a/b", ["a", "a/b"]],
28 |     ])("mkdir %s %s prints %j", async (flags, operand, created) => {
29 |       using dir = tempDir("mkdir-verbose", {});
30 |       const cwd = String(dir);
31 | 
32 |       expect(await mkdir(cwd, flags, operand)).toEqual({
                                                    ^
error: expect(received).toEqual(expected)

  {
-   "exitCode": 0,
-   "stderr": "",
-   "stdout": 
- "/tmp/mkdir-verbose_A8Up5B/dir
+   "exitCode": 1,
+   "stderr": 
+ "mkdir: illegal option -- verbose
  "
  ,
+   "stdout": "",
  }

- Expected  - 4
+ Received  + 4

      at <anonymous> (/workspace/bun/test/js/bun/shell/commands/mkdir.test.ts:32:48)
(fail) bunshell mkdir > verbose > mkdir --verbose dir prints ["dir"] [0.83ms]
27 |       ["--verbose -p", "a/b", ["a", "a/b"]],
28 |     ])("mkdir %s %s prints %j", async (flags, operand, created) => {
29 |       using dir = tempDir("mkdir-verbose", {});
30 |       const cwd = String(dir);
31 | 
32 |       expect(await mkdir(cwd, flags, operand)).toEqual({
                                                    ^
error
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/shell/commands/mkdir.test.ts
bun test v1.4.0 (eff1f3e4b)

test/js/bun/shell/commands/mkdir.test.ts:
(pass) bunshell mkdir > verbose > mkdir -v dir prints ["dir"] [119.83ms]
(pass) bunshell mkdir > verbose > mkdir --verbose dir prints ["dir"] [50.17ms]
(pass) bunshell mkdir > verbose > mkdir -pv a/b prints ["a","a/b"] [48.73ms]
(pass) bunshell mkdir > verbose > mkdir -p --verbose a/b prints ["a","a/b"] [58.10ms]
(pass) bunshell mkdir > verbose > mkdir --parents --verbose a/b prints ["a","a/b"] [59.70ms]
(pass) bunshell mkdir > verbose > mkdir --verbose --parents a/b prints ["a","a/b"] [45.67ms]
(pass) bunshell mkdir > verbose > mkdir --verbose -p a/b prints ["a","a/b"] [53.85ms]
(pass) bunshell mkdir > verbose > mkdir --parents a/b prints nothing without a verbose flag [53.48ms]
(pass) bunshell mkdir > verbose > mkdir -p a/b prints nothing without a verbose flag [55.42ms]
(pass) bunshell mkdir > verbose > mkdir  a/b prints nothing without a verbose flag [53.96ms]
(pass) bunshell mkdir > verbose > the misspelling --vebose is 
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 929ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/13] cxx obj/unified/UnifiedSource-src_jsc_bindings-1.cpp.o
[2/13] cxx obj/unified/UnifiedSource-src_uws_sys-0.cpp.o
[3/13] cxx obj/unified/UnifiedSource-src_jsc_bindings-5.cpp.o
[4/13] cxx obj/unified/UnifiedSource-src_jsc_bindings_node-0.cpp.o
[5/13] cxx obj/unified/UnifiedSource-src_jsc_bindings-4.cpp.o
[6/13] cxx obj/src/jsc/bindings/bindings.cpp.o
[7/13] cxx obj/unified/UnifiedSource-src_jsc_bindings-0.cpp.o
[8/13] cxx obj/unified/UnifiedSource-src_jsc_bindings-3.cpp.o
[9/13] gen generated_host_exports.rs
generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 240 extern-C blocks audited
[9/13] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_shell_parser v0.0.0 (/workspace/bun/src/shell_parser)
�[1m�[92m   Compiling�[0m bun_uws_sys v0.0.0 (/workspace/bun/src/uws_sys)
�[1m�[92m   Compiling�[0m bun_ast v0.0.0 (/workspace/bun/src/
... (truncated)
diff hotspot
src/runtime/shell/builtin/mkdir.rs       |  3 +-
 test/js/bun/shell/commands/mkdir.test.ts | 85 ++++++++++++++++++++++++++++++++
 2 files changed, 86 insertions(+), 2 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                      reads  edits  tests
src/runtime/shell/builtin/mkdir.rs            1      1      0
test/js/bun/shell/commands/mkdir.test.ts      1      6      0

root cause · written by the author bot

The mkdir builtin's long-flag parser matched the misspelled literal --vebose instead of --verbose, so mkdir --verbose fell through to short-flag parsing of -verbose, failed on the first byte, and exited 1 without creating anything. The fix corrects the literal in parse_long to --verbose (dropping the misspelling and the comment that described it as intentional), so the flag now sets the same verbose state as -v, and a new test file covers -v and --verbose alone and combined with -p/--parents, along with rejection of the old spelling.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Limit details: You’ve used all 5 included reviews currently available under your plan.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cd376cad-aa7e-4d31-9c2e-6b8f499eaefc

📥 Commits

Reviewing files that changed from the base of the PR and between bcba472 and 02fcc44.

📒 Files selected for processing (2)
  • src/runtime/shell/builtin/mkdir.rs
  • test/js/bun/shell/commands/mkdir.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Reproduced with bun 1.4.0 on Linux and Windows: mkdir --verbose dir exits 1 with mkdir: illegal option -- verbose, while mkdir -v dir and the misspelled mkdir --vebose dir succeed.

Fix and test are in this PR (#39221): test/js/bun/shell/commands/mkdir.test.ts has 8 of 15 cases failing on 1.4.0 and all 15 passing with this change.

Comment thread test/js/bun/shell/commands/mkdir.test.ts

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the macOS EEXIST text is now branched on isMacOS, which resolves the prior finding.

What was reviewed:

  • parse_long change is a one-string literal fix (--vebose--verbose); Opts doc comment already documents -v, --verbose.
  • Test file placement matches sibling per-builtin files in test/js/bun/shell/commands/; array interpolation of ${words} splits into separate argv words (verified against handle_template_value in shell_body.rs), and the empty-flags case produces mkdir a/b with the pre-created a/ parent.
  • Path assertions use join() so separators are platform-correct; the only per-OS string (EEXIST) now branches, and isMacOS is exported from harness.
Extended reasoning...

Overview

Two files. src/runtime/shell/builtin/mkdir.rs changes one byte-string comparison in Opts::parse_long from b"--vebose" to b"--verbose" and deletes the comment that called the typo intentional. test/js/bun/shell/commands/mkdir.test.ts is a new 84-line test file with 15 parametrized cases covering -v/--verbose alone and with -p/--parents in every order, the silent (no-verbose) baseline, -p on an existing directory, EEXIST without -p, and rejection of the old --vebose spelling.

Prior review

The previous run flagged that the EEXIST assertion pinned File exists, which diverges on macOS where coreutils_error_map's DELTA table returns File or folder exists. Commit 110ffae added isMacOS from harness and branches the expected suffix — exactly the suggested fix, keeping full precision per platform rather than loosening to a regex. isMacOS is exported at test/harness.ts:20.

Security risks

None. The change tightens the accepted long-flag set for a shell builtin's option parser (accepts the documented spelling, stops accepting the undocumented typo). No new inputs reach any syscall; the verbose codepath was already reachable via -v.

Level of scrutiny

Low for the Rust change — a single literal correction with the doc comment three lines above already saying -v, --verbose. Medium for the test file, which is where the review effort went: verified array interpolation semantics (${words} with a string array spreads as space-separated argv per shell_body.rs:497-528; an empty array appends nothing so the "" case becomes mkdir a/b), that tempDir + describe.concurrent gives each case its own directory, that join() handles path separators, and that readdirSync assertions are single-entry/empty so ordering can't matter.

Other factors

Test placement follows the established test/js/bun/shell/commands/<builtin>.test.ts convention (13 siblings). $.nothrow() at module top is the same pattern used in 13 other files under test/js/bun/shell/. The --vebose rejection test asserts only the mkdir: illegal option -- prefix, which the PR description notes is deliberate (the trailing option text is being fixed separately) — that's a reasonable loosening with a stated reason. No bugs from the bug-hunting system this run.

@robobun

robobun commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 6:00 PM PT - Aug 15th, 2026

@robobun, your commit 02fcc44 is building: #98912

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the earlier macOS EEXIST-text concern is resolved (02fcc44 loosened it to a regex that also accommodates #39189's operand-vs-resolved-path change, with the rationale commented inline).

What was reviewed:

  • The one-word b"--vebose"b"--verbose" change in parse_long; -v and --parents arms are untouched.
  • Grepped the repo for other --vebose references — none outside this PR, so no existing test relies on the old spelling.
  • Test file matches sibling shell/commands/*.test.ts conventions ($.nothrow(), describe.concurrent, tempDir, array interpolation for flags); the two intentionally-loose stderr assertions each pin exit code, empty stdout, and a non-vacuous message shape.
Extended reasoning...

Overview

A one-word typo fix in src/runtime/shell/builtin/mkdir.rs: parse_long now matches b"--verbose" instead of the misspelled b"--vebose", and the misleading "intentional" comment is deleted. A new test/js/bun/shell/commands/mkdir.test.ts covers -v/--verbose alone and combined with -p/--parents in both orders, silent output without a verbose flag, -p on an existing dir, EEXIST without -p, and rejection of the old --vebose spelling.

Security risks

None. This only changes which byte string a flag parser matches; no new syscalls, no user-controlled data flowing anywhere new.

Level of scrutiny

Low. The Rust change is a two-line literal correction whose effect is fully described by the Opts doc comment directly above it and by GNU/BSD mkdir. The behavior it enables (self.verbose = true) is identical to the existing -v short flag, so all downstream verbose-output code is already exercised.

Other factors

  • My previous review flagged that the EEXIST assertion pinned Linux-only text and would fail on macOS. The author fixed this in 110ffae (branched on isMacOS), then in 02fcc44 relaxed it to expect.stringMatching(/^mkdir: (?:.*[\\/])?dir: File (?:or folder )?exists\n$/) so it also survives #39189 (which switches the error to the operand as written) and the separate macOS EEXIST-wording fix. The comment above the assertion documents why; stdout and exitCode remain exact, which is the invariant this PR owns (verbose stays off on failure). The review thread is resolved.
  • Grepped the whole repo for vebose — the only remaining hits are in this PR's own test (the rejection case), so dropping support for the misspelling breaks nothing.
  • The test file follows the same conventions as ls.test.ts/rm.test.ts in the same directory: module-level $.nothrow(), describe.concurrent with per-test tempDir, toEqual on a {stdout, stderr, exitCode} object. Array interpolation ($mkdir ${words} ${operand}``) spreads flags as separate args, and filter(Boolean) handles the empty-flags case.
  • No CODEOWNERS entry covers src/runtime/shell/.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant