Skip to content

Disable the transpiler cache when its directory does not fit in a path buffer - #39705

Closed
robobun wants to merge 4 commits into
mainfrom
farm/80184e26/transpiler-cache-long-cache-dir
Closed

robobun wants to merge 4 commits into
mainfrom
farm/80184e26/transpiler-cache-long-cache-dir

Conversation

@robobun

@robobun robobun commented Aug 20, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • With a HOME or XDG_CACHE_HOME of about 4 KB, running any file of 4 KiB or more dies with panic: index out of bounds: the len is 4095 but the index is 4095 (or range end index 4099 out of range for slice of length 4095), Crashed while parsing <file>. Bun 1.3 ran the file.
  • RuntimeTranspilerCache::really_get_cache_dir (src/jsc/RuntimeTranspilerCache.rs:705 and :730 before this change) joins the variable into a fixed PathBuffer with join_abs_string_buf_z, which does not check the size. The panic is in normalize_string_buf during the cache lookup for the first file large enough to be cached.

Fix

  • Join with join_abs_string_buf_checked into the first MAX_PATH_BYTES - CACHE_FILE_NAME_RESERVE bytes of the buffer. A directory that does not fit returns 0, which the caller already treats as "cache disabled" (the state BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 produces). An over-long BUN_RUNTIME_TRANSPILER_CACHE_PATH takes the same exit instead of being cut to 4095 bytes.
  • The reserve is derived from the name write_cache_filename writes (CACHE_FILE_SUFFIX, CACHE_FILE_NAME_LEN, now used there too), so get_cache_file_path always has room for the separator, the name and the NUL.
  • Verified: test/cli/run/transpiler-cache.test.ts, "disables the cache when the cache directory does not fit in a path buffer" (HOME, XDG_CACHE_HOME, explicit path, at 4095 and 4200 bytes). It panics on the current build and passes with the fix. The other 13 tests still pass.

Background

  • The runtime transpiler cache stores the transpiled output of files of at least 4 KiB as <hash>.pile under $XDG_CACHE_HOME/bun/@t@, ~/.bun/install/cache/@t@ or BUN_RUNTIME_TRANSPILER_CACHE_PATH. Every failure in it is meant to be silent: Bun transpiles instead.
  • PathBuffer is a fixed [u8; MAX_PATH_BYTES] (4096 on Linux, 1024 on macOS). An environment variable has no such limit.
  • join_abs_string_buf_checked is the existing size checked joiner. It returns None when the normalized result does not fit.
Notes
  • Crash window on Linux before the fix: HOME from about 4073 bytes up (HOME plus /.bun/install/cache/@t@ no longer fits). A 4061 byte HOME already worked: only the file name failed to fit, which write_cache_filename reports as ENOSPC and the callers swallow. The cut down BUN_RUNTIME_TRANSPILER_CACHE_PATH ended in that ENOSPC path on every lookup too, so its visible behavior (no cache) is unchanged, it is just decided in one place now. That variant of the test passes before and after, the HOME and XDG_CACHE_HOME variants are the ones that panic.
  • CACHE_FILE_NAME_RESERVE is 29 bytes in debug builds (separator, 16 hex digits, .debug.pile, NUL) and 23 in release.
  • The macOS branch moved from #[cfg] to cfg! so the three candidate part lists share one join call. Candidate order and the "no HOME means no cache" result (test "disables the cache instead of falling back to the shared temp directory") are unchanged.
  • Touches the same function as the open runtime transpiler cache: per-uid root, ownership check, mandatory payload hashes #35747 (per-uid cache root) and the same suffix strings as transpiler cache: give the Rust line its own .pile2 filename namespace #31803 (.pile2). Both are independent of this fix. Whichever lands second needs a small rebase.
  • Checked by hand on the debug build: HOME, XDG_CACHE_HOME and BUN_RUNTIME_TRANSPILER_CACHE_PATH of 4096 bytes run an 84 KB file, and normal values still write a .debug.pile entry.

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

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

test/cli/run/transpiler-cache.test.ts:
(pass) transpiler cache > works [847.37ms]
(pass) transpiler cache > works with empty files [738.23ms]
(pass) transpiler cache > ignores files under the minimum cache size [359.76ms]
(pass) transpiler cache > it is indeed content addressable [1103.84ms]
(pass) transpiler cache > doing 50 buns at once does not crash [2133.28ms]
(pass) transpiler cache > disables the cache instead of falling back to the shared temp directory [762.25ms]
188 |       );
189 |     }
190 | 
191 |     const results = await Promise.all(variants.map(vars => bunRun(join(temp_dir, "a.js"), { ...env, ...vars })));
192 |     for (const result of results) {
193 |       expect(result).toSpawn("long-cache-dir");
                           ^
error: expect(received).toSpawn(expectedStdout)

Expected process to exit with code 0 but got 134 (signal: SIGABRT)
stderr: ============================================================
Bun Debug v1.4.0 (4c689909e) Linux x64
Linu
... (truncated)

release without fix: 1 FAILED
bun test v1.4.0-canary.1 (4c689909e)

test/cli/run/transpiler-cache.test.ts:
(pass) transpiler cache > works [32.43ms]
(pass) transpiler cache > works with empty files [29.93ms]
(pass) transpiler cache > ignores files under the minimum cache size [16.83ms]
(pass) transpiler cache > it is indeed content addressable [51.89ms]
(pass) transpiler cache > doing 50 buns at once does not crash [116.07ms]
(pass) transpiler cache > disables the cache instead of falling back to the shared temp directory [29.35ms]
188 |       );
189 |     }
190 | 
191 |     const results = await Promise.all(variants.map(vars => bunRun(join(temp_dir, "a.js"), { ...env, ...vars })));
192 |     for (const result of results) {
193 |       expect(result).toSpawn("long-cache-dir");
                           ^
error: expect(received).toSpawn(expectedStdout)

Expected process to exit with code 0 but got 134 (signal: SIGABRT)
stderr: ============================================================
Bun Canary v1.4.0-canary.1 (4c689909e) Linux x64
Linux Kernel v6.17.0 | glibc v2.41
CPU: sse42 popcnt avx avx2 avx512
Args: "/workspace/bun/build/release/bun" "/tmp/bun.test.1TvVhb/a.js"
Features: jsc 
Builtins: 
... (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/transpiler-cache.test.ts
bun test v1.4.0 (4c689909e)

test/cli/run/transpiler-cache.test.ts:
(pass) transpiler cache > works [790.97ms]
(pass) transpiler cache > works with empty files [742.88ms]
(pass) transpiler cache > ignores files under the minimum cache size [354.73ms]
(pass) transpiler cache > it is indeed content addressable [1129.52ms]
(pass) transpiler cache > doing 50 buns at once does not crash [2335.93ms]
(pass) transpiler cache > disables the cache instead of falling back to the shared temp directory [760.04ms]
(pass) transpiler cache > disables the cache when the cache directory does not fit in a path buffer [421.46ms]
(pass) transpiler cache > works if the cache is not user-readable [1134.51ms]
(pass) transpiler cache > works if the cache is not user-writable [378.47ms]
(pass) transpiler cache > does not inline process.env [748.71ms]
(pass) transpiler cache > --feature flag invalidates cache [2164.85ms]
(pass) transpiler cache > a cached entry point does not change how later modules load > require.extension
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 596ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/7] cxx obj/src/jsc/bindings/ZigGlobalObject.cpp.o
[2/7] gen generated_host_exports.rs
generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 241 extern-C blocks audited
[3/7] gen cpp.rs (cppbind)
[3/7] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_jsc v0.0.0 (/workspace/bun/src/jsc)
�[1m�[92m   Compiling�[0m bun_ast_jsc v0.0.0 (/workspace/bun/src/ast_jsc)
�[1m�[92m   Compiling�[0m bun_js_parser_jsc v0.0.0 (/workspace/bun/src/js_parser_jsc)
�[1m�[92m   Compiling�[0m bun_http_jsc v0.0.0 (/workspace/bun/src/http_jsc)
�[1m�[92m   Compiling�[0m bun_semver_jsc v0.0.0 (/workspace/bun/src/semver_jsc)
�[1m�[92m   Compiling�[0m bun_css_jsc v0.0.0 (/workspace/bun/src/css_jsc)
�[1m�[92m   Compiling�[0m bun_sourcemap_jsc v0.0.0 (/workspace/bun/src/sourcemap_jsc)
�[1m�[92m   Compiling�[0m bun_sys_jsc v0.0.0 (/workspace/bun/src/sys_jsc)
�[1m�[
... (truncated)
diff hotspot
src/jsc/RuntimeTranspilerCache.rs     | 101 +++++++++++++++-------------------
 test/cli/run/transpiler-cache.test.ts |  28 ++++++++++
 2 files changed, 71 insertions(+), 58 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                   reads  edits  tests
src/jsc/RuntimeTranspilerCache.rs          7      9      0
test/cli/run/transpiler-cache.test.ts      3      4      0

…h buffer

The cache directory is joined from XDG_CACHE_HOME or HOME, which can be
longer than a PathBuffer. The unchecked joiner then panics inside
normalize_string_buf while the first file above MINIMUM_CACHE_SIZE is
loaded. Use the checked joiner into a buffer that also reserves room for
the cache file name, and treat a directory that does not fit, including
an over-long BUN_RUNTIME_TRANSPILER_CACHE_PATH, as "cache disabled".
@robobun

robobun commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

Reproduced on the 1.4 canary (4c68990) and on the debug build: HOME=/hhh...h (4096 bytes) running an 84 KB file panics with index out of bounds: the len is 4095 but the index is 4095, the same through XDG_CACHE_HOME. Bun 1.3.13 runs the file. With this branch the file runs and no cache entry is written, normal values still write one. The new test in test/cli/run/transpiler-cache.test.ts panics on the canary and passes with the branch.

@coderabbitai

coderabbitai Bot commented Aug 20, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e9a008a0-bb10-49d4-b338-fedb4a9e6d7b

📥 Commits

Reviewing files that changed from the base of the PR and between 25edd97 and da43b73.

📒 Files selected for processing (2)
  • src/jsc/RuntimeTranspilerCache.rs
  • test/cli/run/transpiler-cache.test.ts

Included review availability: Your plan provides up to 5 included reviews per hour; 1 remains after this review.


Walkthrough

The transpiler cache now centralizes filename sizing, validates cache paths against fixed buffer limits, rejects overlong paths, and uses checked joins for default directories. Tests cover borderline and oversized paths without cache creation.

Changes

Transpiler cache path handling

Layer / File(s) Summary
Centralized cache filename sizing
src/jsc/RuntimeTranspilerCache.rs
Debug and release suffixes now define shared filename and reservation lengths. Filename generation validates against the fixed length.
Checked cache-directory resolution
src/jsc/RuntimeTranspilerCache.rs, test/cli/run/transpiler-cache.test.ts
Cache-directory resolution reserves space for the complete filename, rejects overlong configured paths, validates default joins, and tests borderline and oversized paths without creating cache entries.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly states that the transpiler cache is disabled when its directory exceeds the path buffer size.
Description check ✅ Passed The description explains the problem, fix, verification steps, test coverage, and resulting behavior in sufficient detail.

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

@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 fix is correct and self-contained. One minor test-hygiene nit inline (Windows USERPROFILE), but it doesn't affect the platforms where the panic actually reproduces.

Extended reasoning...

Overview

This PR fixes a panic in RuntimeTranspilerCache::really_get_cache_dir when HOME / XDG_CACHE_HOME is close to MAX_PATH_BYTES — the unchecked join_abs_string_buf_z overran the fixed PathBuffer. The fix swaps to join_abs_string_buf_checked into a sub-slice that reserves room for the cache filename, and returns 0 (cache disabled) when the directory doesn't fit. It also lifts CACHE_FILE_SUFFIX / CACHE_FILE_NAME_LEN / CACHE_FILE_NAME_RESERVE to module constants so the reserve is derived from what write_cache_filename actually writes, and collapses the three per-platform join calls into one via cfg!. A regression test covers all three env-var routes at 4095 and 4200 bytes.

Security risks

None. This is defensive bounds handling on environment-variable input feeding a fixed path buffer; the failure mode changes from a panic to "cache silently disabled", which is the intended contract for every other failure in this module.

Level of scrutiny

Moderate — a small refactor of a single function plus constant extraction. I verified:

  • join_abs_string_buf_checked returns Some(&buf[..len]) starting at index 0 (both fast and slow paths), so buf[len] = 0 places the NUL correctly.
  • The #[cfg(target_os = "macos")] → cfg!(...) rewrite preserves candidate order and the "no HOME → cache disabled" fallthrough exactly.
  • Boundary arithmetic: at the maximum cache_dir_len == MAX_PATH_BYTES - CACHE_FILE_NAME_RESERVE, get_cache_file_path writes the NUL at buf[MAX_PATH_BYTES - 1], which is in bounds.
  • The over-long BUN_RUNTIME_TRANSPILER_CACHE_PATH branch now returns 0 instead of silently truncating to MAX_PATH_BYTES - 1 — visible behavior (no cache) is unchanged, just decided earlier.

Other factors

The one inline nit (HOME variant on Windows reads USERPROFILE, so it inherits the runner's real profile) is a test-hermeticity issue only; on Windows MAX_PATH_BYTES is ~98 KB so a 4 KB path never triggers the guard regardless, and the panic only reproduces on Linux/macOS where the test is correct. The fix itself is sound on all platforms.

Comment thread test/cli/run/transpiler-cache.test.ts Outdated
Comment thread src/jsc/RuntimeTranspilerCache.rs Outdated
Comment thread src/jsc/RuntimeTranspilerCache.rs Outdated
Comment thread src/jsc/RuntimeTranspilerCache.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 — the USERPROFILE fix from the earlier review is in, and the checked-join change looks correct.

What was reviewed:

  • join_abs_string_buf_checked return semantics vs. the manual NUL write and CACHE_FILE_NAME_RESERVE arithmetic — the exact-fit case (len == dir_buf.len()) still leaves room for separator + name + NUL at buf[MAX_PATH_BYTES-1].
  • #[cfg(target_os = "macos")] → cfg! refactor preserves candidate order and the no-HOME → disabled result.
  • The three comment-cop flags are on ordinary 2–4 line doc comments (derived-constant descriptions and the function contract), not workaround justifications; the FileSystem comment at :712 predates this PR with one word changed.
Extended reasoning...

Overview

Two files: src/jsc/RuntimeTranspilerCache.rs (really_get_cache_dir and write_cache_filename) and a new test in test/cli/run/transpiler-cache.test.ts. The fix swaps the unchecked join_abs_string_buf_z for join_abs_string_buf_checked into a sub-slice sized to leave room for the cache file name, and returns 0 (cache disabled) when the environment-derived directory does not fit. The suffix and filename length are hoisted to constants so write_cache_filename and the reserve share one source of truth. The three duplicate join calls collapse into one via cfg! instead of #[cfg].

Security risks

None. The input is an environment variable the user already controls; the failure mode being fixed was a bounds-checked panic, not memory corruption. The change tightens bounds handling and disables an optional cache on overflow.

Level of scrutiny

Medium. Runtime path-buffer arithmetic in Rust, so a mistake would panic rather than corrupt, and the transpiler cache is designed to be silently skippable on any error. I traced the reserve arithmetic end to end: get_cache_dir now returns at most MAX_PATH_BYTES - CACHE_FILE_NAME_RESERVE, and get_cache_file_path then writes at most through buf[MAX_PATH_BYTES - 1]. The join_abs_string_buf_checked slow path allows joined.len() == buf.len(), which still fits because the NUL goes into the reserved tail of the full PathBuffer, not dir_buf. NLL releases the dir_buf reborrow before each buf[...] = 0 write, so this compiles.

Other factors

  • The #[cfg] → cfg! change is behavior-preserving: on macOS with HOME set the old code always returned from the cfg block, and both call the same env_var::HOME.get(), so the fallback .bun/install/cache branch was already unreachable there.
  • My earlier finding (Windows reads USERPROFILE, not HOME) was addressed in 25edd97 and the thread is resolved.
  • The three open github-actions comment-cop flags fire on the new constant doc comments, the extended really_get_cache_dir doc comment, and the pre-existing FileSystem comment (one word changed). None of them are workaround justifications; the file already carries far longer comments. I'm treating them as heuristic noise.
  • Test covers all three env-var routes at two lengths, runs the six spawns concurrently, and the sibling tests in the file establish that toSpawn fails on non-zero exit (so a panic would be caught). The PR body confirms the HOME/XDG variants panic on the current build.

Comment thread src/jsc/RuntimeTranspilerCache.rs
@robobun

robobun commented Aug 20, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:00 AM PT - Aug 20th, 2026

✅ @robobun, your commit da43b73a5e95ba904fa3e9d812f40115c100a4f6 passed in Build #101571! 🎉


🧪   To try this PR locally:

bunx bun-pr 39705

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

bun-39705 --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 — the checked joiner and reserved-tail arithmetic look correct, and the earlier USERPROFILE feedback is addressed. The remaining inline nit is only about the wording of a test comment.

The unresolved comment-cop flag on RuntimeTranspilerCache.rs:680 is on the two-line doc comment for really_get_cache_dir; that's a normal function doc, not a workaround justification, so I'd treat it as a false positive.

Extended reasoning...

Overview

Two files: src/jsc/RuntimeTranspilerCache.rs (hoists CACHE_FILE_SUFFIX/CACHE_FILE_NAME_LEN/CACHE_FILE_NAME_RESERVE constants, rewrites write_cache_filename to use them, and rewrites really_get_cache_dir to join into buf[..MAX_PATH_BYTES - CACHE_FILE_NAME_RESERVE] via join_abs_string_buf_checked, returning 0 on overflow) and test/cli/run/transpiler-cache.test.ts (adds a 6-variant test over HOME/USERPROFILE, XDG_CACHE_HOME, and BUN_RUNTIME_TRANSPILER_CACHE_PATH at 4095 and 4200 bytes).

Correctness of the refactor

  • Candidate order is unchanged: BUN_RUNTIME_TRANSPILER_CACHE_PATH → XDG_CACHE_HOME → HOME → 0. The macOS #[cfg]→cfg! fold is behavior-preserving: on macOS the old code returned from the Library/Caches arm before ever reaching the .bun/install/cache arm, so collapsing them into an if cfg!(macos) inside a single HOME branch yields the same result on every platform.
  • Buffer arithmetic checks out: join_abs_string_buf_checked's slow path (resolve_path.rs:1698) bounds the result at <= dir_buf.len(), so len <= MAX_PATH_BYTES - CACHE_FILE_NAME_RESERVE and buf[len] = 0 is in range; then in get_cache_file_path, total = len + 1 + CACHE_FILE_NAME_LEN <= MAX_PATH_BYTES - 1, so buf[total] = 0 is in range and write_cache_filename's ENOSPC branch is now unreachable in practice (kept as a defensive guard, which is fine). The explicit-path branch's dir.len() > dir_buf.len() guard makes the subsequent copy_from_slice and NUL write safe.
  • join_abs_string_buf_checked does not NUL-terminate (unlike the _z variant it replaces); the new code writes buf[len] = 0 explicitly, so get_cache_dir's contract is preserved.

Security risks

None. The input is process environment (already trusted at this layer), and the change strictly narrows what reaches the fixed buffer. The old truncation of BUN_RUNTIME_TRANSPILER_CACHE_PATH to 4095 bytes is replaced with a clean disable, which is the safer direction.

Level of scrutiny

Medium. Native path-buffer arithmetic on user-controllable input, but the fix is a straight swap to an existing size-checked helper plus derived constants, and the failure mode on any mistake is a Rust bounds-check panic (the very thing being fixed), not memory corruption. The one refactor beyond the minimal fix — folding three join calls into one via cfg! — is small and traced above.

Other factors

  • My previous review (Windows USERPROFILE leaking through bunEnv) was addressed in 25edd97; the HOME variant now sets both.
  • Three comment-cop flags were resolved in 32ac850; the fourth, still open on line 680, targets the two-line really_get_cache_dir doc comment and reads as a bot false positive.
  • The one finding this run is a nit on the trailing existsSync(cache_dir) comment wording — the assertion itself is not vacuous (it guards the undefined override), only the comment overstates its scope. Not merge-blocking.
  • The test panics on the pre-fix build per the PR description, and the other 13 tests in the file still pass, so the USE_SYSTEM_BUN=1 criterion is met for the HOME/XDG variants.

Comment thread test/cli/run/transpiler-cache.test.ts 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 — small, well-scoped panic fix with a regression test.

Checked: join_abs_string_buf_checked returns Option and does not NUL-terminate, so the manual buf[len] = 0 is required and always in bounds (dir length is capped at MAX_PATH_BYTES - CACHE_FILE_NAME_RESERVE, leaving room for SEP + filename + NUL in get_cache_file_path). The #[cfg] → cfg! refactor preserves the macOS candidate order (XDG → ~/Library/Caches → disabled). Both earlier review notes (USERPROFILE on Windows, the trailing-assertion comment) were addressed in 25edd97 and da43b73.

Extended reasoning...

Overview

Two files: src/jsc/RuntimeTranspilerCache.rs swaps join_abs_string_buf_z for the size-checked join_abs_string_buf_checked when resolving the transpiler cache directory from BUN_RUNTIME_TRANSPILER_CACHE_PATH / XDG_CACHE_HOME / HOME, and lifts the cache-file suffix and name length into module constants so really_get_cache_dir can reserve exactly enough room for get_cache_file_path to append the separator, hex hash, suffix and NUL. test/cli/run/transpiler-cache.test.ts gains one test spawning six variants (three env vars × two lengths) that used to panic on Linux/macOS.

Security risks

None. The change only decides whether the on-disk transpiler cache is disabled when an environment variable is pathologically long; the disabled state was already reachable via BUN_RUNTIME_TRANSPILER_CACHE_PATH=0. No new filesystem paths are derived, no untrusted data is parsed, and the fallback (transpile without caching) is the safe default.

Level of scrutiny

Low-to-medium. This is a targeted fix for a reachable panic in a non-critical subsystem (the cache is a performance optimization; every failure in it is already designed to be silent). The diff is ~60 lines net, mostly consolidating three duplicated join calls into one and hoisting literals into named constants. I verified the arithmetic: with cache_dir_len ≤ MAX_PATH_BYTES - (1 + CACHE_FILE_NAME_LEN + 1), get_cache_file_path writes SEP at cache_dir_len, the filename at cache_dir_len+1, and NUL at cache_dir_len+1+CACHE_FILE_NAME_LEN ≤ MAX_PATH_BYTES-1, so no index is out of range. The #[cfg(target_os = "macos")] → cfg! change is behavior-preserving: on macOS, HOME set → Library/Caches, HOME unset → disabled, exactly as before (the old second HOME branch was dead on macOS because the first one returned).

Other factors

The two comments I left on earlier revisions (Windows USERPROFILE leak making the HOME variant non-hermetic; the trailing assertion's comment overstating what it checks) were both addressed in follow-up commits and the threads are resolved. The comment-cop bot's long-comment complaints were shortened in 32ac850. The new test follows the file's existing conventions (bunRun, toSpawn, Buffer.alloc over .repeat, concurrent spawns via Promise.all), and the PR description confirms the other 13 tests in the file still pass. No design decisions here that need a human — this is a mechanical bounds fix at the layer that owns the invariant.

@robobun

robobun commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of #43067. It fixes this trigger with the shared checked path helpers and carries the tests from this pull request.

@robobun robobun closed this Sep 17, 2026
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.

2 participants