Skip to content

bake: support fileSystemRouterTypes roots outside the project root - #33203

Closed
robobun wants to merge 3 commits into
mainfrom
farm/ccd7de96/bake-router-root-outside-project-root
Closed

robobun wants to merge 3 commits into
mainfrom
farm/ccd7de96/bake-router-root-outside-project-root

Conversation

@robobun

@robobun robobun commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Bun.serve({ app }) registers no usable routes when a fileSystemRouterTypes[].root resolves outside the project root, which is the normal monorepo shape (root: "../web/pages" while serving from apps/api).

Reproduction (cwd is /repo/apps/api, routes live in /repo/apps/web/pages):

Bun.serve({
  port: 0,
  app: {
    framework: {
      fileSystemRouterTypes: [
        { root: "../web/pages", style: "nextjs-pages", serverEntryPoint: "./server-entry.ts" },
      ],
    },
  },
});
  • Debug/assert builds abort at startup:
    panic: assertion failed: strings::has_prefix(&ty.abs_root, root) (FrameworkRouter.rs, init_empty)
  • Release builds start fine but register the routes under the wrong patterns, so every request returns 404 Not Found (in the example above, pages/index.ts is registered as /pages instead of /).
  • With a root that resolves to an ancestor of the project root (root: ".."), release builds crash instead:
    panic: range start index 18446744073709551611 out of range for slice of length 14

Cause

FrameworkRouter::scan_inner computed each file's path relative to the project root, then sliced off abs_root.len() - root.len() - 1 bytes to get the route pattern. That arithmetic is only meaningful when the router root is inside the project root; init_empty's debug assertion documented the assumption but nothing validated user input against it. For a sibling root the slice lands mid-path (wrong patterns, so 404s); for an ancestor root the subtraction underflows (panic).

Fix

Derive the route pattern directly from the file's path relative to the router type's own abs_root (the directory the scan walks), with no dependence on the project root. The project-root-relative path is still used to label files in the route collision error, computed on that error path. The has_prefix assertion is gone because the invariant it asserted is no longer required by anything.

Route syntax errors now report against the same string the pattern parser saw, since TinyLog.cursor_at is an index into it; the previous code shifted the cursor by abs_root.len() - root.len(), which is meaningless for out-of-root roots.

Tests

test/bake/framework-router.test.ts gains two cases (sibling root, ancestor root) that spawn Bun.serve({ app }) and assert both a static and a dynamic route resolve with the expected bodies.

bun bd test test/bake/framework-router.test.ts   # 37 pass
USE_SYSTEM_BUN=1 bun test test/bake/framework-router.test.ts -t "router root"  # both fail on 1.4.0 (404s / index-out-of-range panic)

Existing coverage over in-project roots is unchanged: test/bake/dev/esm.test.ts (17 pass) and the rest of framework-router.test.ts.

Route patterns were derived by slicing the project-root-relative path by
abs_root.len() - root.len(), which assumes every router root is inside
the project root. A root like "../web/pages" (a sibling package in a
monorepo) tripped the has_prefix debug assertion in init_empty, and in
release builds produced wrong route patterns (every request 404s) or an
index underflow panic when the resolved root was shorter than the
project root. Derive the pattern from the path relative to the router
type's own abs_root instead.
@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 3 minutes

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: a240bc38-d968-47ab-b51a-6ac34b21ae50

📥 Commits

Reviewing files that changed from the base of the PR and between dcb74f8 and 7856565.

📒 Files selected for processing (1)
  • test/bake/framework-router.test.ts

Walkthrough

FrameworkRouter's route scanning now computes relative paths from each Type's abs_root instead of the router's root, altering values passed to syntax error and collision error callbacks; init_empty asserts abs_root is absolute. A new test suite validates router roots located outside the project root.

Changes

FrameworkRouter Path Computation

Layer / File(s) Summary
rel_path derived from Type.abs_root
src/runtime/bake/FrameworkRouter.rs
Documentation clarifies root's purpose, init_empty now asserts Type.abs_root is absolute, and scan_inner rebuilds rel_path relative to each Type.abs_root with normalization and posix conversion.
Error callback path handling
src/runtime/bake/FrameworkRouter.rs
Syntax error and too-many-params callbacks use the newly computed rel_path; collision error reporting recalculates a display path relative to self.root, replacing prior full_rel_path usage and cursor-offset adjustments.
Out-of-root router integration tests
test/bake/framework-router.test.ts
New imports (bunEnv, bunExe, tempDir) and a describe block spawn a Bun server against fixtures where fileSystemRouterTypes[].root points to a sibling or parent directory, asserting correct route responses and process exit codes.

Possibly related PRs

  • oven-sh/bun#33056: Also modifies scan_inner-driven route-scanning logic in FrameworkRouter.rs, changing how scan results and paths are derived and handled.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: supporting router roots outside the project root.
Description check ✅ Passed The PR covers the change, cause, fix, and verification details; it is mostly complete even though the exact verification heading is missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@github-actions github-actions Bot added the claude label Jul 1, 2026
@robobun

robobun commented Jul 1, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 9:00 PM PT - Jul 1st, 2026

❌ @robobun, your commit 7856565 has 7 failures in Build #67748 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 33203

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

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

I didn't find any bugs — the simplified relative-path derivation looks correct and the tests are solid — but this reworks how bake derives route patterns and changes what path string is shown in router syntax errors (now abs_root-relative with a leading / instead of project-root-relative), so a bake maintainer should confirm that display change is intended.

Extended reasoning...

Overview

This PR touches src/runtime/bake/FrameworkRouter.rs (scan_inner and init_empty) and adds two integration tests to test/bake/framework-router.test.ts. The core change replaces the old two-step route-pattern computation (path relative to project root, then slice off abs_root.len() - root.len() - 1 bytes) with a direct relative_normalized_buf against the type's own abs_root. The has_prefix(abs_root, root) debug assertion is removed since the invariant it guarded is no longer needed. The route-collision error path now recomputes the project-root-relative display path on demand via path_buffer_pool::get(), and the syntax-error path now passes the exact parsed string (with unmodified cursor_at) to on_router_syntax_error.

Security risks

None identified. The scan still walks only under abs_root (no user-controlled traversal is introduced), the arithmetic that could underflow is removed rather than added, and no external input reaches new sinks. The change strictly narrows the surface where slice indexing can go wrong.

Level of scrutiny

Medium. The diff is small (~40 native LOC net) and the new logic is simpler and easier to prove correct than what it replaces — every scanned file is under abs_root, so relativizing against abs_root cannot produce .. and the leading-/ prefix is unconditionally correct. The two new subprocess tests exercise both the sibling-root (previously wrong patterns → 404) and ancestor-root (previously underflow panic) shapes end-to-end through Bun.serve({ app }), covering static and dynamic routes.

Other factors

There is an intentional, user-visible behavior change on the error path that isn't covered by tests: on_router_syntax_error (which DevServer forwards to TinyLog::print and JSFrameworkRouter embeds in its AggregateError) now receives the abs_root-relative path with a leading / (e.g. /foo/[bad.tsx) instead of the project-root-relative path without one (e.g. pages/foo/[bad.tsx). The PR description calls this out and it fixes an off-by-one in the old cursor shift, but it does change what users see in "is not a valid route" messages for the common in-project-root case. The collision-error path preserves the old project-root-relative labeling. Given bake is an actively-developed framework surface and this is a semantic change to route derivation plus a UX change to error output, I'd like a maintainer familiar with bake to sign off rather than shadow-approving.

@robobun

robobun commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

The syntax-error display change is intentional, to expand on the note in the description: TinyLog.cursor_at is a byte index into whatever string TinyLog::print is given, and that index comes from the pattern parser, which only ever sees the route-root-relative path. The old code translated the cursor with abs_root.len() - root.len(), which is only meaningful when the router root sits inside the project root (and underflows for an ancestor root). Printing the exact string that was parsed keeps the caret aligned for every root shape, so syntax errors now show /blog/[oops.tsx (the path that defines the route pattern) instead of pages/blog/[oops.tsx.

Collision errors keep the project-root-relative label: they have no cursor, and the handler prints the other colliding file project-relative on the next line, so both lines stay comparable.

@coderabbitai coderabbitai 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.

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/bake/framework-router.test.ts`:
- Around line 129-164: The current router tests in framework-router.test.ts only
cover successful sibling and parent directory routing, but the PR also changes
the user-facing labels for syntax and collision errors. Add out-of-root fixtures
in the same test area that exercise a malformed route and a route collision,
then assert the exact caret/label output for each error path. Use the existing
run(String(dir)) style and keep the new assertions anchored to the
framework-router behavior so the changed error labels are covered by automated
tests.
🪄 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: d80b815a-b7f1-47a8-ae84-56474d42eba1

📥 Commits

Reviewing files that changed from the base of the PR and between eba370b and dcb74f8.

📒 Files selected for processing (2)
  • src/runtime/bake/FrameworkRouter.rs
  • test/bake/framework-router.test.ts

Comment thread test/bake/framework-router.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.

I didn't find any issues, but this reworks how scan_inner derives route patterns and changes the user-visible syntax-error label (now router-root-relative instead of project-root-relative even for in-root cases), so it's worth a human sign-off on that behavior change.

Extended reasoning...

Overview

Two files touched:

  • src/runtime/bake/FrameworkRouter.rs: init_empty drops the has_prefix(abs_root, root) debug assertion in favor of is_absolute(abs_root); scan_inner now derives each file's route pattern directly from its path relative to the router type's abs_root instead of computing it relative to the project root and slicing off abs_root.len() - root.len() - 1 bytes. The syntax-error path no longer offsets log.cursor_at and reports against the exact string the parser saw; the collision-error path recomputes a project-root-relative display label on demand via path_buffer_pool::get().
  • test/bake/framework-router.test.ts: three new spawned-subprocess tests covering a sibling router root, a parent-directory router root, and the syntax/collision error labels for an out-of-root router root.

Security risks

None identified. This is startup-time filesystem route discovery; the paths involved come from the resolver's directory walk under abs_root, not from untrusted network input. No auth, crypto, or permission surfaces are touched.

Level of scrutiny

Moderate. The core diff is a ~30-line simplification that removes brittle length arithmetic, and I traced that the new computation produces the same rel_path as the old code for the previously-working in-root case (both yield /<path-under-abs_root>). However, this is the route-pattern derivation for Bun.serve({ app })'s bake router, and it deliberately changes the user-visible syntax-error label for all roots (e.g. "/[oops.ts" instead of "pages/[oops.ts"). The author's rationale — TinyLog.cursor_at indexes into the parsed string, and the old offset translation underflows for ancestor roots — is sound, but it's a design call a maintainer should confirm rather than a pure bugfix.

Other factors

  • The bug hunter found no issues.
  • CodeRabbit's one review request (negative coverage for the changed error labels) was addressed in 7856565 and the thread is resolved.
  • path_buffer_pool::get() on the collision path matches existing repo idiom.
  • Test coverage is solid: static + dynamic route resolution for both out-of-root shapes, plus exact-string assertions on both changed error labels; existing in-root tests are untouched.
  • No CODEOWNERS entry matches src/runtime/bake/.

@robobun

robobun commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

CI status for 7856565 (build 67748): every red lane is a test this PR does not touch, and each of them is currently red on unrelated PRs too.

  • test/bake/dev/production.test.ts > works with sourcemaps - error thrown in React component: times out at 90s on every platform. The same timeout shows up on every recent PR build regardless of branch (67799, 67800, 67801, 67812, all different authors/areas), so it is a main-wide breakage, not something introduced here. The child bun build --app prints the expected render error and then never exits; that happens after route scanning, and this test's router root is inside the project root, where this PR derives byte-identical patterns. There is a separate PR in flight for that prerender hang.
  • test/js/node/zlib/zlib.test.js > brotli streaming encode doesn't wait for entire input (15s timeout) and test/js/node/test/parallel/test-net-connect-memleak.js: both are in the CI flaky annotations across recent PR builds and are unrelated subsystems. The release binary built from this PR's base commit runs the brotli test in ~4s locally, so there is no perf regression behind the timeout.

test/bake/framework-router.test.ts, which contains the tests added by this PR (sibling root, parent root, and the error-label coverage), passed on every lane in both CI runs of this PR. I already used one retrigger, so I will leave CI as is rather than keep re-running it; this is ready for review and can be re-run or rebased once the prerender hang fix lands on main.

@robobun

robobun commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-07-01, it conflicts with main, and its last CI run failed. This is not a judgment on the fix itself. If the problem still reproduces on a current build, reopen this PR after a rebase or open a new one against main.

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