Skip to content

fix(bake): validate app.bundlerOptions values are objects#30653

Closed
robobun wants to merge 1 commit into
mainfrom
farm/4434cab3/bake-bundler-options-validation
Closed

fix(bake): validate app.bundlerOptions values are objects#30653
robobun wants to merge 1 commit into
mainfrom
farm/4434cab3/bake-bundler-options-validation

Conversation

@robobun

@robobun robobun commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Fuzzer fingerprint: f6f8b134d3813972

What

Bun.serve({ app: { bundlerOptions: ... } }) hit a debug assertion in JSValue.get() when:

  • bundlerOptions was not an object
  • bundlerOptions.{server,client,ssr} was not an object
  • minify was neither a boolean nor an object (e.g. a number, or false which fell through the isBoolean() and asBoolean() check)
Bun.serve({ app: { bundlerOptions: { ssr: { minify: 1225 } } } });
// panic(main thread): reached unreachable code
// bun.debugAssert (JSValue.get asserts target.isObject())

Fix

Validate these values are objects before reading properties from them and throw a TypeError instead, matching the existing handling in JSBundler.zig.

Bun.serve would hit a debug assertion when app.bundlerOptions,
its server/client/ssr sub-options, or minify were not objects
(e.g. minify: 1225 or minify: false). Validate the types and
throw a proper TypeError instead, matching Bun.build's handling
of the minify option.
@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@robobun has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 9 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ac9598de-d985-4406-87a6-47f439ea2d5d

📥 Commits

Reviewing files that changed from the base of the PR and between b8ecc78 and d38b886.

📒 Files selected for processing (2)
  • src/bake/bake.zig
  • test/js/bun/http/bun-serve-args.test.ts

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

@robobun

robobun commented May 14, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 6:25 PM PT - May 13th, 2026

@robobun, your commit d38b886 has 1 failures in Build #54155 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 30653

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

bun-30653 --bun

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. bake: validate bundlerOptions values are objects before property access #30125 - Same fix for validating bundlerOptions/server/client/ssr are objects and fixing the minify: false fallthrough bug in bake.zig
  2. Validate app.bundlerOptions types in Bun.serve #30402 - Same bundlerOptions type validation in bake.zig with identical test file changes
  3. Bun.serve: validate app.bundlerOptions is an object #30647 - Same bundlerOptions object validation and minify handling fix in bake.zig

🤖 Generated with Claude Code

@robobun

robobun commented May 14, 2026

Copy link
Copy Markdown
Collaborator Author

Duplicate of #30125.

@robobun robobun closed this May 14, 2026
@robobun robobun deleted the farm/4434cab3/bake-bundler-options-validation branch May 14, 2026 01:25
Comment on lines +686 to +691
test("minify as false does not crash", () => {
expect(() =>
// @ts-expect-error
serve({ app: { bundlerOptions: { server: { minify: false } } } }),
).not.toThrow("reached unreachable code");
});

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.

🟡 This assertion is ineffective: .not.toThrow("reached unreachable code") passes when a different error is thrown, and this config (missing framework) will throw 'app' is missing 'framework' right after bundlerOptions parsing — so the test passes for the wrong reason and would still pass if the minify: false fix were reverted. CLAUDE.md also explicitly forbids tests that check for absence of panic messages. Assert the expected error instead, e.g. .toThrow("'app' is missing 'framework'"), which proves execution got past minify parsing.

Extended reasoning...

What the bug is

The new test 'minify as false does not crash' asserts:

expect(() =>
  serve({ app: { bundlerOptions: { server: { minify: false } } } }),
).not.toThrow("reached unreachable code");

This violates the explicit rule in the repo's CLAUDE.md (line ~126): "NEVER write tests that check for no 'panic' or 'uncaught exception' or similar in the test output. These tests will never fail in CI." The string "reached unreachable code" is the panic message produced by bun.debugAssert — i.e. exactly the kind of negative-panic check the guidelines forbid.

Why the assertion is ineffective

There are two independent reasons this test cannot catch a regression of the minify: false fix:

  1. .not.toThrow(<msg>) passes on a different error. Per Jest/Bun semantics, .not.toThrow("X") passes if the callback throws nothing or throws an error whose message does not contain "X". It only fails if the callback throws an error containing "X".

  2. A debug-build panic isn't a catchable JS error. The original failure was bun.debugAssert inside JSValue.get(), which aborts the process. An in-process expect().not.toThrow() cannot observe a process abort — the test runner just dies. And in release builds, debugAssert is compiled out entirely.

The specific code path that hides the regression

Looking at UserOptions.fromJS in src/bake/bake.zig, after bundlerOptions is parsed the very next step is:

const framework = try Framework.fromJS(
    try config.get(global, "framework") orelse {
        return global.throwInvalidArguments("'" ++ api_name ++ "' is missing 'framework'", .{});
    },
    ...
);

The test config { app: { bundlerOptions: { server: { minify: false } } } } has no framework key, so serve() will throw 'app' is missing 'framework'. That error message does not contain "reached unreachable code", so .not.toThrow("reached unreachable code") passes.

Step-by-step proof that the test passes even if the fix is reverted

Suppose the minify handling is reverted to the buggy form (isBoolean() and asBoolean() falling through to property reads on false):

  • Debug build: JSValue.get() hits bun.debugAssert(target.isObject()) and the process aborts. No JS error is thrown; the test file simply crashes (which CLAUDE.md notes does not reliably fail CI). The .not.toThrow(...) assertion never gets to evaluate a thrown error.
  • Release build: bun.debugAssert is a no-op. get("whitespace") etc. on the JS value false either returns nothing useful or proceeds; eventually execution reaches the framework check and throws 'app' is missing 'framework'. .not.toThrow("reached unreachable code")passes.

In neither build does the assertion fail. The test therefore provides zero regression coverage for the bug it was written to guard against.

Impact

This is a test-quality issue rather than a production bug, but it ships a permanently-green test that gives false confidence. If the minify: false handling regresses, this test will not catch it.

How to fix

Change the assertion to positively verify that bundlerOptions parsing succeeded and execution reached the next validation step:

test("minify as false does not crash", () => {
  expect(() =>
    // @ts-expect-error
    serve({ app: { bundlerOptions: { server: { minify: false } } } }),
  ).toThrow("'app' is missing 'framework'");
});

This proves minify: false was accepted (no panic, no "Expected minify to be a boolean or an object" error) and parsing continued to the framework check. Alternatively, supply a valid framework and assert it does not throw at all — but asserting the specific downstream error is the simplest change that turns this into an effective regression test.

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