Skip to content

resolver: suppress spurious "directory mismatch" warning from --tsconfig-override - #34980

Closed
robobun wants to merge 7 commits into
mainfrom
claude/farm/8061134f/tsconfig-override-dirname-fd
Closed

robobun wants to merge 7 commits into
mainfrom
claude/farm/8061134f/tsconfig-override-dirname-fd

Conversation

@robobun

@robobun robobun commented Jul 21, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Fix

  • Pass FD::INVALID as dirname_fd whenever tsconfig_override is set, so the absolute open is taken directly. The non-override branch still passes fd, where it is the directory that actually contains tsconfig.json.
  • This matches the extends chain a few lines below, which already passes FD::INVALID.
  • FD::ZERO was a resolver-local alias for Fd::INVALID with this one remaining use, so the alias is removed.
  • Verified with test/cli/run/tsconfig-override.test.ts: the six existing bun run cases had an if (!stderr.includes("Internal error: directory mismatch")) guard tolerating the warning; that guard is removed so they assert stderr === "", and a bun build --tsconfig-override case is added.
    • USE_SYSTEM_BUN=1 bun test test/cli/run/tsconfig-override.test.ts: 7 fail (warning on stderr)
    • bun bd test test/cli/run/tsconfig-override.test.ts: 7 pass

Background

  • The resolver caches one DirInfo per directory; dir_info_uncached fills it in, and when it is created for a directory that has a tsconfig.json it parses that file. With --tsconfig-override the override is parsed once instead, attached to the root DirInfo (the parent.is_none() case), so every directory inherits it.
  • When STORE_FILE_DESCRIPTORS is on, the resolver keeps directories open and reads files with openat(dir_fd, basename) to save path lookups. read_file_with_allocator takes an optional dirname_fd for this; an invalid fd means "open by absolute path".

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

fails on main (without fix)
ASAN without fix: 7 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/run/tsconfig-override.test.ts
bun test v1.4.0 (17674b2d4)

test/cli/run/tsconfig-override.test.ts:
60 |       successProc.stderr.text(),
61 |       successProc.exited,
62 |     ]);
63 | 
64 |     expect(successStdout).toContain("success from custom tsconfig");
65 |     expect(successStderr).toBe("");
                               ^
error: expect(received).toBe(expected)

- ""
+ "Internal error: directory mismatch for directory "/tmp/run-tsconfig-override_3GYLYO/custom-tsconfig.json", fd 4[/]. You don't need to do anything, but this indicates a bug.
+ "

- Expected  - 1
+ Received  + 2

      at <anonymous> (/workspace/bun/test/cli/run/tsconfig-override.test.ts:65:27)
(fail) bun run --tsconfig-override > should use custom tsconfig for path resolution [1246.51ms]
 98 |     });
 99 | 
100 |     const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
101 | 
102 |     expect(stdout).toContain("42");
103 |     expect(stderr).toBe("");
                         ^
error: expect(rece
... (truncated)

release without fix: 7 FAILED
bun test v1.4.0-canary.1 (da3851e57)

test/cli/run/tsconfig-override.test.ts:
60 |       successProc.stderr.text(),
61 |       successProc.exited,
62 |     ]);
63 | 
64 |     expect(successStdout).toContain("success from custom tsconfig");
65 |     expect(successStderr).toBe("");
                               ^
error: expect(received).toBe(expected)

- ""
+ "Internal error: directory mismatch for directory "/tmp/run-tsconfig-override_TiNCfm/custom-tsconfig.json", fd 3. You don't need to do anything, but this indicates a bug.
+ "

- Expected  - 1
+ Received  + 2

      at <anonymous> (/workspace/bun/test/cli/run/tsconfig-override.test.ts:65:27)
(fail) bun run --tsconfig-override > should use custom tsconfig for path resolution [95.93ms]
 98 |     });
 99 | 
100 |     const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
101 | 
102 |     expect(stdout).toContain("42");
103 |     expect(stderr).toBe("");
                         ^
error: expect(received).toBe(expected)

- ""
+ "Internal error: directory mismatch for directory "/tmp/run-tsconfig-relative_C9oZsM/config/custom.json", fd 3. You don't need to do anythi
... (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/cli/run/tsconfig-override.test.ts
bun test v1.4.0 (17674b2d4)

test/cli/run/tsconfig-override.test.ts:
(pass) bun run --tsconfig-override > should use custom tsconfig for path resolution [1026.25ms]
(pass) bun run --tsconfig-override > should work with relative tsconfig path [545.81ms]
(pass) bun run --tsconfig-override > should work with monorepo-style paths [438.32ms]
(pass) bun run --tsconfig-override > should work with nested directories and complex paths [610.89ms]
(pass) bun run --tsconfig-override > should handle extending tsconfig with overrides [588.81ms]
(pass) bun run --tsconfig-override > should work from different working directories [443.92ms]
(pass) bun build --tsconfig-override > resolves through the override without an fd warning on stderr [383.09ms]

 7 pass
 0 fail
 26 expect() calls
Ran 7 tests across 1 file. [8.64s]
__F:0:S:0

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     17674b2d4d
  features     baseline

22 deps, 107 codegen, 1176 objects in 1447ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1238] gen bindgenv2
[2/1238] gen ErrorCode+*.h
[3/1238] fetch zlib
[zlib] up to date
[4/1238] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[5/1238] gen JSBuffer.lut.h
Generating /workspace/bun/build/release/codegen/JSBuffer.lut.h from /workspace/bun/src/jsc/bindings/JSBuffer.cpp
[6/1238] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingConstants.lut.h from /workspace/bun/src/jsc/bindings/ProcessBindingConstants.cpp
[7/1238] fetch tinycc
[tinycc] up to date
[8/1237] gen .bind.ts → GeneratedBindings.cpp
[9/1237] gen JSEvent.lut.h
Generating /workspace/bun/build/release/codegen/JSEvent.lut.h from /workspace/bun/src/jsc/bindings/webcore/JSEvent.cpp
[10/1237] gen ProcessBindingBuffer.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingBuffer.lut.h from /workspace/bun/src/jsc/bindings/Pr
... (truncated)
diff hotspot
src/resolver/resolver.rs               | 12 ++----
 test/cli/run/tsconfig-override.test.ts | 77 ++++++++++++++++++++++++----------
 2 files changed, 58 insertions(+), 31 deletions(-)

gate history · 3 passed · 0 rejected · iteration 4

evidence per changed file
file                                    reads  edits  tests
src/resolver/resolver.rs                    4      2      0
test/cli/run/tsconfig-override.test.ts      1      1      0

dir_info_uncached reads the tsconfig override when parent.is_none(),
i.e. while scanning the filesystem root. It passed that directory fd
as dirname_fd to parse_tsconfig, so read_file_with_allocator did
openat(root_fd, basename(override_path)) which fails with ENOENT and
prints the "Internal error: directory mismatch" fallback warning on
every run. The absolute-path retry succeeds, so the override still
applies; only the warning is spurious.

Pass FD::ZERO for the override path so the absolute open is taken
directly, matching the extends-chain call site. Drop the test
workaround that tolerated the warning so the existing six cases
assert an empty stderr.
@coderabbitai

coderabbitai Bot commented Jul 21, 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: 21 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: 0102f800-6d12-40b3-8236-756afec871df

📥 Commits

Reviewing files that changed from the base of the PR and between bdb7382 and 17674b2.

📒 Files selected for processing (2)
  • src/resolver/resolver.rs
  • test/cli/run/tsconfig-override.test.ts

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

@robobun

robobun commented Jul 21, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 11:05 PM PT - Aug 12th, 2026

❌ @robobun, your commit 17674b2 has some failures in Build #93992 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 34980

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

bun-34980 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 3 issues this PR may fix:

  1. --tsconfig-override show internal error #22023 - Reports the exact "Internal error: directory mismatch" warning when using --tsconfig-override, which is the bug this PR eliminates
  2. Bun.build() tsconfig option doesn't resolve path aliases (CLI works) #26793 - Notes that bun build --tsconfig-override works but prints the "Internal error: directory mismatch" warning
  3. jsxImportSource from nested tsconfig ignored when running from workspace root #28605 - Reports that using --tsconfig-override as a workaround logs the "Internal error: directory mismatch" warning

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #22023
Fixes #26793
Fixes #28605

🤖 Generated with Claude Code

@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 dirname_fd passed to parse_tsconfig now correctly falls back to FD::ZERO when --tsconfig-override is set, so read_file_with_allocator opens by absolute path instead of attempting openat(root_fd, basename).

What was reviewed

  • Traced tsconfig_path assignment: override branch is only entered when parent.is_none() and tsconfig_override.is_some(), so gating on tsconfig_override.is_none() exactly discriminates the two sources of tsconfig_path.
  • Confirmed FD::ZERO aliases Fd::INVALID (resolver.rs:252), so dirname_fd.is_valid() is false and read_file_with_allocator (lib.rs:2512) takes the absolute-open path — matching the extends-chain call below.
  • Test change only removes the pre-existing if (!stderr.includes("directory mismatch")) guard; the underlying expect(stderr).toBe("") assertions were already there. ASAN + release evidence in the PR shows 6 fail before / 6 pass after.
Extended reasoning...

Overview

One-line semantic change in src/resolver/resolver.rs (dir_info_uncached): when computing the dirname_fd argument to parse_tsconfig, add && self.opts.tsconfig_override.is_none() to the existing STORE_FILE_DESCRIPTORS gate so the override path is opened absolutely rather than via openat(fd, basename). The rest of the resolver diff is reindentation from hoisting dirname_fd into a let — the error-handling body is byte-identical. The test diff removes six if (!stderr.includes("Internal error: directory mismatch")) guards that had been tolerating the bug.

Correctness

I traced the three ways tsconfig_path can be populated at this site (resolver.rs:6372–6417): (a) tsconfig_override.is_none() → tsconfig.json/jsconfig.json found in the directory being scanned, where fd is that directory's fd, so openat(fd, basename) is correct; (b) tsconfig_override.is_some() && parent.is_none() → the override path, where fd is the filesystem root's fd, so openat would miss; (c) tsconfig_override.is_some() && parent.is_some() → tsconfig_path stays None and the block isn't entered. Gating on tsconfig_override.is_none() therefore passes fd exactly in case (a) and FD::ZERO exactly in case (b). FD::ZERO is defined as Fd::INVALID at resolver.rs:252, so read_file_with_allocator at lib.rs:2512 sees !dirname_fd.is_valid() and opens by absolute path directly — no fallback, no warning. This mirrors how the extends-chain call a few lines below already passes an invalid fd for absolute-path opens.

Security risks

None. This changes which fd is used to open a config file the user explicitly named on the CLI; no new inputs are trusted and no validation is weakened.

Level of scrutiny

Low-to-moderate. The resolver is hot-path code, but this change only affects the --tsconfig-override branch (a once-per-process config load, gated on parent.is_none()). The behavioral outcome was already correct — the absolute-path fallback in read_file_with_allocator was succeeding — this just stops the spurious diagnostic by taking the right path first. No allocation, ownership, or error-handling semantics change.

Other factors

The PR includes fails-before/passes-after evidence on both ASAN-debug and release builds across all six existing --tsconfig-override tests. The test tightening is a strict strengthening of pre-existing assertions (the expect(stderr).toBe("") calls were already there, just conditionally skipped), not new bare-stderr assertions. The 3-line comment added at the change site is within repo guidelines and explains the non-obvious fd-vs-override-parent mismatch.

@robobun

robobun commented Jul 21, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI status for 17674b2 (build 93992): every lane that ran passed (177/177 jobs), including test/cli/run/tsconfig-override.test.ts on all of them. The build is marked failed only because the two darwin 14 aarch64 test shards expired after waiting 60 minutes for an agent; the darwin aarch64 test queue is currently backlogged fleet-wide and the same thing is happening to other open PRs. Nothing in this diff is darwin-specific (the same code path runs on every platform), so this is ready for review as is.

FD::ZERO was a resolver-local alias for Fd::INVALID and this was its
last use, so spell it directly (as the extends-chain call below already
does) and drop the alias. Adds a bun build --tsconfig-override case
whose stderr must be empty, alongside the existing bun run cases.
Comment thread src/resolver/resolver.rs Outdated

@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 — targeted fix that passes FD::INVALID as dirname_fd when --tsconfig-override is set, so read_file_with_allocator opens the override by absolute path instead of openat(root_fd, basename).

What was reviewed:

  • Traced tsconfig_path assignment: when tsconfig_override.is_some() it's only set in the parent.is_none() branch, so the non-override case (where fd is correct) is unaffected.
  • Confirmed read_file_with_allocator (src/resolver/lib.rs:2339) takes the direct absolute-path open when dirname_fd.is_valid() is false — no fallback warning.
  • Verified the removed FdZero trait had no other users; FD::ZERO was just an alias for FD::INVALID.
  • The comment-cop flag on line 6536 looks like a false positive — the 3-line comment explains a non-local invariant (why fd is the wrong directory here), not a workaround. Trimming it to one line would be fine but isn't blocking.
Extended reasoning...

Overview

Two files: src/resolver/resolver.rs (one-condition change in dir_info_uncached's tsconfig loading, plus removal of the now-unused FdZero trait alias) and test/cli/run/tsconfig-override.test.ts (removed six if (!stderr.includes("Internal error: directory mismatch")) guards so all cases now assert stderr === "", and added a bun build --tsconfig-override test).

The mechanism is sound: tsconfig_path is set to the override only when parent.is_none() (scanning the filesystem root), so the local fd is the root directory's fd, not the override file's parent. read_file_with_allocator at lib.rs:2339-2361 gates the openat(dirname_fd, basename(path)) fast path on dirname_fd.is_valid(); passing FD::INVALID sends it straight to the absolute open_file(path) at line 2360, which is what the ENOENT fallback was reaching anyway — just without the warning. The non-override branch (tsconfig_override.is_none()) still passes fd, which is correct there since tsconfig_path is <dir>/tsconfig.json and fd is <dir>'s fd.

Security risks

None. This changes which fd is passed to an internal file-open helper for a CLI-provided config path; no new inputs, no privilege changes, no path validation weakened.

Level of scrutiny

Low-to-medium. The functional change is a single boolean condition on which fd to pass; the rest is reindentation of the existing error-handling match and dead-code removal (FdZero had one caller, replaced inline with FD::INVALID). The error branches (ENOENT → "Cannot find tsconfig", other → "Cannot read file") are byte-identical to before. This matches the extends-chain call further down which already passes FD::INVALID for the same reason.

Other factors

  • Tests: six existing tests were tightened from tolerating the warning to asserting clean stderr — a strict strengthening. A new bun build test covers the bundler entry point per the sibling-entry-point rule. PR evidence shows the tightened tests fail on the unfixed debug build and pass with the fix.
  • CI: the diff's own test file passed on every lane; the one CI failure (bun-server.test.ts websocket-idle on darwin aarch64) is unrelated and reproduces on main per the robobun follow-up.
  • The comment-cop bot flagged the new 3-line comment as "paragraph-long workaround justification". I read it as a legitimate why-comment for a non-local invariant (you'd otherwise have to trace fd's provenance up through dir_info_uncached to see it's the root fd here), not a workaround — the code is the fix. Could be one line, but that's cosmetic.

robobun added a commit that referenced this pull request Aug 13, 2026
The resolver hunk and its tests were the same one-line fix as #34980,
which is the PR for #22023. This branch now only changes the JSX
precedence between bunfig.toml / --jsx-* / Bun.build({jsx}) and
tsconfig.json.

@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 — targeted one-condition fix with the tolerance guards removed from the existing tests so they actually assert it.

Reviewed: the dir_info_uncached branch structure — tsconfig_path only comes from the override when tsconfig_override.is_some(), so gating fd on .is_none() is exact. Confirmed read_file_with_allocator falls through to the absolute-path open when dirname_fd.is_valid() is false (lib.rs:2339). Verified FdZero/FD::ZERO have no remaining users repo-wide. The comment-cop note was addressed in 17674b2 (now a single line) and the thread is resolved.

Extended reasoning...

Overview

Two files touched. In src/resolver/resolver.rs, the parse_tsconfig call inside dir_info_uncached now passes FD::INVALID as dirname_fd when --tsconfig-override is set, instead of the fd of the directory currently being scanned (which is / when the override is loaded, since that happens at parent.is_none()). The dead FdZero trait alias for Fd::INVALID is removed. In test/cli/run/tsconfig-override.test.ts, six existing tests drop the if (!stderr.includes("Internal error: directory mismatch")) guard so they assert stderr === "" unconditionally, and a seventh bun build --tsconfig-override case is added with the override file in a subdirectory.

Correctness

I traced the enclosing if/else if at resolver.rs:6487–6531. The tsconfig.json/jsconfig.json lookup branch is entered only when tsconfig_override.is_none(), and there fd is genuinely the containing directory. The else if parent.is_none() branch is the only place tsconfig_path becomes the override path, and that requires tsconfig_override.is_some(). So the new condition && self.opts.tsconfig_override.is_none() cleanly separates the two: the non-override path keeps the openat fast path, the override path falls through to open_file(path, ...) in read_file_with_allocator (lib.rs:2359) via the dirname_fd.is_valid() check at lib.rs:2339 — the same route the extends-chain resolution already uses. Grep confirms FD::ZERO/FdZero have no other references, so the trait removal is safe.

Security risks

None. This changes which fd is passed to an internal file-open helper for a CLI-provided config path; no new inputs, no privilege boundaries crossed.

Level of scrutiny

Low-to-moderate. The functional change is a single boolean guard on an argument that already had a working absolute-path fallback (the override was applying correctly before; only the warning was spurious). The test changes strictly tighten assertions rather than loosening them, which is the right direction. CI (robobun) reports the touched test file green on every lane; the lone darwin-aarch64 failure in bun-server.test.ts is unrelated and reproduces on main.

Other factors

The comment-cop bot flagged a long comment on an earlier revision; commit 17674b2 shortened it to one line and the thread is marked resolved. PR evidence shows the tightened tests fail on a debug build without the fix and pass with it, satisfying the USE_SYSTEM_BUN=1 gate. No prior reviews from me on this PR.

robobun added a commit that referenced this pull request Aug 13, 2026
The resolver hunk and its tests were the same one-line fix as #34980,
which is the PR for #22023. This branch now only changes the JSX
precedence between bunfig.toml / --jsx-* / Bun.build({jsx}) and
tsconfig.json.
@robobun

robobun commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of #38606, which removes the same Internal error: directory mismatch ... line (it is linked to #22023 as well) by parsing the --tsconfig-override file outside the directory scan, and also makes Bun.build({ tsconfig }) honor the option.

Verified against a debug build of #38606: the line is gone on both the bun run and bun build paths, and this PR's test file passes unchanged there. The six bun run assertions here are already in #38606, and the bun build coverage was added to the existing --tsconfig-override tests in test/bundler/cli.test.ts on that branch (3cbef6c).

@robobun robobun closed this Aug 15, 2026
vincentkoc added a commit to openclaw/bun that referenced this pull request Sep 27, 2026
* fix(resolver): avoid unrelated fd for tsconfig overrides

Read explicit tsconfig overrides by their absolute path instead of borrowing the directory descriptor from the resolver walk. Keep ordinary config discovery and override precedence unchanged.

Adapt the narrow FD repair from oven-sh#34980 and require clean stderr for CLI run/build override resolution, including unused malformed disk configs.

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>

* docs(changelog): place override fd fix beside descriptor entry

---------

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
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.

--tsconfig-override show internal error

2 participants