Skip to content

bun run: print the package.json parse error instead of "Script not found" - #41700

Open
robobun wants to merge 1 commit into
mainfrom
robobun/a77998cc/run-print-package-json-parse-error
Open

robobun wants to merge 1 commit into
mainfrom
robobun/a77998cc/run-print-package-json-parse-error

Conversation

@robobun

@robobun robobun commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • When the project's package.json does not parse, bun run <script> prints only error: Script not found "<script>" and exits 1. The parse error that bun -e, bun build, and bun install print for the same file never appears. npm run prints EJSONPARSE and names the file.
  • The cause is the not-found exit in Run::exec (src/runtime/cli/run_command.rs:2661). The resolver records the parse error in ctx.log and returns a DirInfo with no package_json. The script lookup sees no scripts and prints "Script not found" without that log.

Fix

  • On the not-found exit, print ctx.log to stderr when it holds errors. This is the same dump configure_env_for_run and boot_failed_exit already use in this file.
  • When no package.json loaded at all (enclosing_package_json is None) and the log explains why, skip "Script not found". The parse error is the message. A missing script in a file that did not load is not a fact bun can report.
  • When a package.json did load (for example the project's own file is valid and an ancestor's is broken), "Script not found" is still printed after the log. The script really is absent from the file bun used.
  • Verified: test/cli/run/run_command.test.ts (two new tests, stock bun fails both). Also ran test/cli/run/if-present.test.ts.

Background

  • Run::exec calls configure_env_for_run, which asks the resolver for the cwd DirInfo. The resolver walks up the directory tree and parses each package.json it finds. A parse failure goes to the resolver's log (PackageJSON::parse in src/resolver/package_json.rs:461) and the directory is treated as having no package.json.
  • ctx.log is the process-lifetime bun_ast::Log that the resolver writes to. Other exits in run_command.rs print it before their own error line. The not-found exit did not.
Notes

Reproduction with stock bun (1.4.3):

printf '{"name": "p", "scripts": {"s": "echo hi"' > package.json
bun run s      # error: Script not found "s"
bun -e '1'     # error: Expected "}" but found end of file, at package.json:1:40

With this change, bun run s prints the parse error with the file path and line:column, and exits 1.

bun run ./missing.js with the same broken package.json prints the parse error and then Module not found "./missing.js". That message is about the file argument, so it stays.

Not covered here: a package.json that is a directory. The resolver only parses regular files, so nothing is logged and the output stays Script not found. Also not covered: bun build failing on a broken ancestor package.json when the project's own file is valid. That is a resolver-level decision about ancestor manifests and is separate from this exit path.


no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/run/run_command.test.ts

…und"

The resolver records a package.json that fails to parse in the log and
returns a DirInfo without it. The script lookup then reported
"Script not found" and never printed that log. Print the log on the
not-found exit. When no package.json loaded at all, the parse error is
the message and "Script not found" is not printed.
@robobun

robobun commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

Status: reproduced with stock bun 1.4.3 (printf '{"name": "p", "scripts": {"s": "echo hi"' > package.json && bun run s prints only Script not found). The fix prints the parse error on the not-found exit. Tests in test/cli/run/run_command.test.ts fail on stock bun and pass with this branch.

@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 14 days. After that, they cost $0.25 per reviewed file.

Or wait 49 seconds for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 819072f7-18db-4112-800e-9b43e02e63b6

📥 Commits

Reviewing files that changed from the base of the PR and between d316760 and ab202f1.

📒 Files selected for processing (2)
  • src/runtime/cli/run_command.rs
  • test/cli/run/run_command.test.ts

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

@github-actions github-actions Bot added the claude label Sep 6, 2026
@robobun

robobun commented Sep 6, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:24 PM PT - Sep 6th, 2026

❌ @robobun, your commit ab202f1 has 2 failures in Build #111521 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 41700

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

bun-41700 --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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

bstr::BStr::new(target_name),
);
} else {
} else if !(has_log_errors && root_dir.enclosing_package_json.is_none()) {

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.

🟡 (optional) The suppression key is the inherited enclosing_package_json, so the misleading "Script not found" line this PR removes still prints in the common monorepo/workspace case: cwd's own package.json fails to parse, an ancestor's is valid, enclosing_package_json is Some(ancestor), and the user sees the parse error followed by "Script not found" for a script that may well be defined in the file that did not load. Fix: gate suppression on the cwd file being the one that failed (e.g. root_dir.package_json.is_none() when a package.json exists in cwd), not on whether any ancestor loaded.

Extended reasoning...

DirInfo.enclosing_package_json is inherited from the parent chain (src/resolver/resolver.rs:6231-6233) and only overwritten with the directory's own file when that file parses (resolver.rs:6354-6361). In a workspace: /repo/package.json valid, /repo/pkgs/app/package.json malformed, cwd /repo/pkgs/app, bun run dev. The resolver logs the parse error and leaves root_dir.package_json = None, but root_dir.enclosing_package_json = Some(/repo/package.json). At line 2704 has_log_errors && root_dir.enclosing_package_json.is_none() is true && false, so the guard is false and Script not found "dev" prints after the parse error. Base branch printed only Script not found, so this path is strictly better than base — but it is exactly the "missing script in a file that did not load" case the PR description says should show only the parse error, and workspaces (where every package sits under a valid root package.json) are where a broken subdirectory package.json is most likely. The second new test only exercises the reverse layout (cwd valid, ancestor broken), so this…

Verification: nit — the mechanism is exactly as described, but merging leaves the base strictly better on this path too (base showed only "Script not found" with no parse error at all; this PR adds the parse error and merely fails to also suppress the trailing line in the monorepo-leaf case). Mechanism verified: - src/resolver/resolver.rs:6231-6233: `info.enclosing_package_json =… | nit — the mechanism is…

Comment on lines +2668 to +2674
let log = unsafe { ctx.log() };
let has_log_errors = log.has_errors();
if has_log_errors {
let _ = log.print(std::ptr::from_mut::<bun_core::io::Writer>(
Output::error_writer(),
));
}

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.

🟣 Pre-existing sibling left unfixed: the new ctx.log() dump is placed after the --if-present early return at line 2658, so bun run --if-present <script> with a broken package.json still exits 0 with no output — the parse error stays swallowed exactly as on the base branch. Fix: dump ctx.log() before the if_present check (or inside it) so every not-found exit surfaces the resolver's package.json error, whether or not --if-present suppresses the "Script not found" line and the nonzero exit.

Extended reasoning...

Run::exec reaches the failure section: line 2657 checks ctx.runtime_options.if_present and returns Ok(true) before line 2668 reads ctx.log(). With printf '{"scripts":{"s":"x"' > package.json && bun run --if-present s, the resolver logs the parse error into ctx.log and returns a DirInfo with enclosing_package_json = None; no script matches; if_present returns success and the process exits 0 with empty stderr. On base this path also swallowed the error, so this is a same-class exit the partial fix leaves behind — the PR's stated goal ("print the package.json parse error") does not reach --if-present users, who are the ones most likely to miss that their scripts silently stopped running. Hoisting the let log = …; if log.has_errors() { log.print(...) } block above line 2657 covers both exits with no behavior change for the tested paths.

Verification: pre-existing — same-class sibling exit left uncovered by the fix. At src/runtime/cli/run_command.rs:2657-2659: if ctx.runtime_options.if_present { return Ok(true); } returns before the newly added log dump at 2668-2674 (let log = unsafe { ctx.log() }; ... log.print(...)). The failure scenario is exactly as stated: with a malformed package.json, the resolver records the parse…

@robobun

robobun commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

This also fixes #42992, which reports the same "Script not found" output for a package.json that does not parse.

This branch has not been deployed

No deployments
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