Skip to content

shell(rm): accept --force as the long form of -f - #39224

Open
robobun wants to merge 2 commits into
mainfrom
farm/77480c1e/shell-rm-force
Open

shell(rm): accept --force as the long form of -f#39224
robobun wants to merge 2 commits into
mainfrom
farm/77480c1e/shell-rm-force

Conversation

@robobun

@robobun robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • The shell's rm builtin rejects --force: rm --force missing exits 1 with rm: illegal option -- -, while rm -f missing exits 0. Same for combinations such as rm --recursive --force dir.
  • parse_flag in src/runtime/shell/builtin/rm.rs (line 494 onwards) matches the long spellings of every other option it implements (--recursive, --verbose, --dir, --interactive=..., --preserve-root) but has no --force arm, so it falls through to IllegalOption. The Opts::force doc comment already lists --force as one of its spellings. After this change every short option rm accepts has its long spelling accepted too.
  • Found by reading the parser while fixing a sibling builtin; there is no user report. It has been this way since the builtin was written (the pre-rewrite Zig parser had the same arm list), so it is not a regression.
  • Repro on bun 1.4.0 and current main:
    bun -e 'import {$} from "bun"; $.nothrow(); console.log((await $`rm --force missing`.quiet()).exitCode)'   # 1, rm: illegal option -- -
    

Fix

  • Add a --force arm to the long-option match that does what the f short-option arm does: set opts.force and reset prompt_behaviour to Never.
  • Correct because rm is a builtin on every platform (there is no system rm to fall back to), and GNU rm defines -f, --force as one option, including the "last of -f/-i wins" rule; sharing the -f body keeps rm -i --force behaving like rm -i -f and rm --force -i like rm -f -i. No other behaviour changes; what force does during removal is untouched.
  • Out of scope: ls and mv have their own option parsers with no long-option handling at all (ls --all, mv --force); that is a different gap in different files, not a missing arm in this match.
  • Verified with test/js/bun/shell/commands/rm.test.ts (--force is the long form of -f): nonexistent operand, existing operand, -i --force, --force --recursive and -r --force on populated directories, and --forc still rejected. Fails on main with the illegal option output above, passes with the fix.
  • Whole rm.test.ts file passes with the debug build.
  • shell(rm): exit 0 silently when -f is given without operands #39225 (rm -f with no operands) edits Rm::next and adds tests to the same file; the two apply cleanly in either order. Since --force sets the same opts.force field that shell(rm): exit 0 silently when -f is given without operands #39225 consults, whichever of the two lands second can add a rm --force with-no-operands case to the other's tests.

Background

  • The builtin parses options in Rm::parse_flag: a ---prefixed argument is matched as a whole word against a fixed list, anything else starting with - is a cluster of single-letter options, and the first argument that matches neither ends option parsing.
  • Opts.force makes the removal tasks treat ENOENT as success; Opts.prompt_behaviour records -i/-I/--interactive=..., and anything other than Never is currently rejected with "-i" is not supported yet once parsing finishes, which is why -f (and now --force) resets it.

no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/bun/shell/commands/rm.test.ts

@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: 34 minutes

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: 6a5fbe80-c7b7-4477-890c-e9f273bfa0dc

📥 Commits

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

📒 Files selected for processing (2)
  • src/runtime/shell/builtin/rm.rs
  • test/js/bun/shell/commands/rm.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 on bun 1.4.0 and on a debug build of main: rm --force missing exits 1 with rm: illegal option -- -, rm -f missing exits 0.

Fix: --force arm in Rm::parse_flag sharing the -f body. Test --force is the long form of -f in test/js/bun/shell/commands/rm.test.ts fails without the src/ change and passes with it; the rest of rm.test.ts passes with the debug build.

CI on a89af9d: 178 of 179 jobs green, including every platform's shell tests. The one red job (debian x64-asan) fails only on test/js/web/fetch/blob.test.ts, which is failing on main as well and is unrelated to this change. Ready for review.

@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 — adds the missing --force long-option arm mirroring the existing -f body.

What was reviewed:

  • Verified the new arm is byte-identical to the b'f' short-option branch (sets opts.force, resets prompt_behaviour to Never), matching the sibling --recursive/--verbose/--dir arms.
  • Test covers nonexistent operand, existing operand, -i --force ordering, combination with --recursive/-r, and the negative case (--forc still rejected).
  • Checked that --force doesn't collide with the flag.len() > 2 guard and that no other long option was missing its arm.
Extended reasoning...

Overview

Five-line addition to Rm::parse_flag in src/runtime/shell/builtin/rm.rs: a b"--force" match arm in the long-option block that sets opts.force = true and opts.prompt_behaviour = PromptBehaviour::Never, then returns ContinueParsing. This is byte-for-byte what the b'f' short-option arm does. The Opts::force field's doc comment already listed --force as a valid spelling, and every other implemented option (--recursive, --verbose, --dir, --interactive=..., --preserve-root) already had its long form — this was simply an omission.

A new test in test/js/bun/shell/commands/rm.test.ts exercises the flag against a nonexistent operand, an existing file, -i --force (verifying the last-wins semantics that -f already has), --force --recursive and -r --force on populated directory trees, and confirms --forc is still rejected with illegal option -- -. The test uses tempDir, asserts full {stdout, stderr, exitCode} objects, and lives alongside the existing force test in the same describe block.

Security risks

None. Option parsing only; no new filesystem behaviour, no change to what force does during removal, no untrusted-input parsing beyond exact byte-string matching.

Level of scrutiny

Low. This is a mechanical gap-fill in a fixed match expression, following the exact pattern of five neighbouring arms. The change cannot affect any input that doesn't literally start with --force as a whole argument. No memory-safety, threading, or GC surface is touched.

Other factors

The test is well-constructed per repo guidelines (hermetic tempdir, exact-value assertions, negative case, variant matrix including flag ordering). PR description confirms the test fails on main and passes with the fix, and that the whole rm.test.ts file passes. No CODEOWNERS coverage on this path. No outstanding reviewer comments.

@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:05 PM PT - Aug 15th, 2026

@robobun, your commit 7ed9946 has some failures in Build #98726 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 39224

That installs a local version of the PR into your bun-39224 executable, so you can run:

bun-39224 --bun

@robobun

robobun commented Aug 15, 2026

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

❌ Your commit a89af9de has 1 failures in Build #98932 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 39224

That installs a local version of the PR into your bun-39224 executable, so you can run:

bun-39224 --bun

@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 — a five-line match arm mirroring the existing -f body, with a thorough test.

What was reviewed

  • The new --force arm is byte-identical to the short -f handling (sets opts.force, resets prompt_behaviour to Never), so rm -i --force and rm --force -i behave exactly like their -f equivalents.
  • Confirmed every other short option in parse_flag already has a long spelling; this was the only gap.
  • Test covers nonexistent/existing operands, -i --force cancellation, --force --recursive and -r --force on populated dirs, and the negative --forc case; uses tempDir, asserts full {stdout, stderr, exitCode}.
Extended reasoning...

Overview

This PR adds a single b"--force" arm to the long-option match in Rm::parse_flag (src/runtime/shell/builtin/rm.rs), setting opts.force = true and opts.prompt_behaviour = PromptBehaviour::Never — exactly what the existing b'f' short-option arm does a few lines below. It also adds one test to test/js/bun/shell/commands/rm.test.ts. The Opts::force doc comment already documented --force as an accepted spelling; the parser just never matched it.

Security risks

None. This is option parsing for a shell builtin; the new arm sets the same two fields the already-accepted -f sets, and touches no removal, path-resolution, or memory-management code.

Level of scrutiny

Low. The change is a mechanical addition of one match arm whose body is copied verbatim from the sibling -f arm in the same function. No control flow, no new state, no allocation, no error paths. The parser structure (whole-word match on ---prefixed args) is unchanged, so existing long options and the IllegalOption fall-through are unaffected.

Other factors

  • The test follows repo conventions: tempDir from harness, await using, exact-object assertions on {stdout, stderr, exitCode}, and covers the variant matrix called out in REVIEW.md — including flag ordering, combination with --recursive/-r, the -i-cancellation semantics, and a negative case (--forc still rejected, file kept).
  • The PR description explicitly verified the test fails on main (illegal option -- -) and passes with the fix, and that the whole rm.test.ts file passes.
  • The earlier CI failure on commit 7ed9946 was addressed by a89af9d (which reordered the test to sit after the recursive test); the second build is in progress but the change is trivially platform-independent.
  • No CODEOWNERS entry covers this path.

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