Skip to content

Honor absolute paths for heap and CPU profiler --*-dir/--*-name options - #32570

Open
robobun wants to merge 4 commits into
mainfrom
farm/b1e7794a/fix-heap-prof-dir-absolute
Open

Honor absolute paths for heap and CPU profiler --*-dir/--*-name options#32570
robobun wants to merge 4 commits into
mainfrom
farm/b1e7794a/fix-heap-prof-dir-absolute

Conversation

@robobun

@robobun robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

Fixes #32568

What

An absolute --heap-prof-dir was resolved relative to the current working directory: the leading separator was stripped and the snapshot landed under $CWD instead of the given directory. The same bug affected --heap-prof-name and the CPU profiler's --cpu-prof-name (an absolute name stripped its leading separator in release and tripped a debug_assert in debug builds). --cpu-prof-dir was already handled correctly.

Repro

mkdir -p /tmp/abs-heap-target
cd /some/other/dir
bun --heap-prof --heap-prof-dir=/tmp/abs-heap-target -e 'globalThis.__x = new Array(10000).fill({x:1})'
# Heap profile written to: /some/other/dir/tmp/abs-heap-target/Heap.….heapsnapshot
#                          ^^^^^^^^^^^^^^^ leading "/" stripped, resolved under CWD

Expected the file in /tmp/abs-heap-target; it was written to $CWD/tmp/abs-heap-target.

Cause

build_output_path appended the directory and filename onto a path already rooted at CWD (AutoAbsPath::init_top_level_dir()):

path.append(config.dir)?;
path.append(filename)?;

For a rooted absolute Path, Path::append trims its input as relative and strips the leading separator (it even debug_assert!(!is_input_absolute(input))s, so an absolute segment here is misuse). Path::join resets the accumulated path when a segment is absolute, which is the behavior that was already used for --cpu-prof-dir.

Fix

Use Path::join instead of Path::append for both the directory and the filename, in both src/jsc/BunHeapProfiler.rs and src/jsc/BunCPUProfiler.rs. Absolute --heap-prof-dir / --heap-prof-name / --cpu-prof-name are now honored; relative values still resolve under CWD.

Verification

test/cli/heap-prof.test.ts and test/cli/run/cpu-prof.test.ts gain cases that run from one directory and write to a separate absolute --*-dir / --*-name, asserting the profile lands at the absolute target and nothing is written under CWD. They fail before this change (under a debug build the stripped-absolute append trips the debug_assert at src/paths/Path.rs:961) and pass after. The existing relative-path tests still pass.

build_output_path appended config.dir onto a path already rooted at the
current directory via AutoAbsPath::init_top_level_dir(). For a rooted
absolute path, Path::append trims the input as relative and strips the
leading separator, so an absolute --heap-prof-dir was resolved under CWD
instead of at the given location. Use Path::join, which resets the
accumulated path when a segment is absolute, matching the CPU profiler.
@robobun

robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 11:25 AM PT - Jun 21st, 2026

@robobun, your commit 6a9a9a5f80e825fa05b80278efd8f38bb0ff457b passed in Build #63813! 🎉


🧪   To try this PR locally:

bunx bun-pr 32570

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

bun-32570 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. --heap-prof-dir mishandles absolute paths #32568 - Reports that --heap-prof-dir strips the leading / from absolute paths, writing heap snapshots to CWD-relative paths instead of the specified absolute directory — exactly the bug this PR fixes

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

Fixes #32568

🤖 Generated with Claude Code

@robobun

robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

Added Fixes #32568 to the description. That issue is the exact bug this fixes.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

More reviews will be available in 6 minutes and 26 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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 credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

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, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c21ef2bd-8a89-412f-93b0-179299bf5a19

📥 Commits

Reviewing files that changed from the base of the PR and between 66cadce and 6a9a9a5.

📒 Files selected for processing (3)
  • src/jsc/BunCPUProfiler.rs
  • test/cli/heap-prof.test.ts
  • test/cli/run/cpu-prof.test.ts

Walkthrough

build_output_path in both BunHeapProfiler.rs and BunCPUProfiler.rs is changed to use path.join(&[...]) instead of path.append(...) when combining configured directories and filenames, so that absolute path segments are preserved. New CLI tests validate these fixes for --heap-prof-dir, --heap-prof-name, and --cpu-prof-name options.

Changes

Absolute path handling in profiler output directories

Layer / File(s) Summary
Heap profiler path construction and tests
src/jsc/BunHeapProfiler.rs, test/cli/heap-prof.test.ts
build_output_path switches from path.append(config.dir)? to path.join(&[config.dir])? for both directory and filename, with a comment explaining that join preserves absolute segments. Two new tests validate that --heap-prof-dir and --heap-prof-name with absolute paths write snapshots to the specified locations, not under CWD.
CPU profiler path construction and tests
src/jsc/BunCPUProfiler.rs, test/cli/run/cpu-prof.test.ts
build_output_path changes from mixed join/append to consistent join for both directory and filename segments, documented with comments explaining absolute segment preservation. A new test verifies that --cpu-prof-name with an absolute path writes the profile to the exact specified location outside CWD.

Possibly related issues

  • --heap-prof-dir mishandles absolute paths #32568: This PR directly implements the fix described in that issue — changing from append to join in both BunHeapProfiler.rs and BunCPUProfiler.rs to correctly preserve absolute paths in --heap-prof-dir and related flags, and adds comprehensive test coverage for both profilers.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: fixing absolute path handling for profiler options.
Description check ✅ Passed The description comprehensively covers what was fixed, how it was verified, includes a reproduction case, explains the root cause, and lists the files modified.
Linked Issues check ✅ Passed The PR directly addresses issue #32568 by replacing path.append with path.join to honor absolute paths in both heap and CPU profilers.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing absolute path handling for profiler options; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@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 — one-line appendjoin swap matching the CPU profiler, with a regression test.

Extended reasoning...

Overview

This PR fixes --heap-prof-dir to honor absolute paths. The change is a single method swap in src/jsc/BunHeapProfiler.rs (path.append(config.dir)path.join(&[config.dir])) plus an explanatory comment, and a new regression test in test/cli/heap-prof.test.ts that asserts the snapshot lands in an absolute target directory rather than under CWD.

Security risks

None. The path being constructed is for a heap snapshot output file, derived from a CLI flag the user themselves supplies on their own machine. No injection, auth, or data-exposure surface is touched.

Level of scrutiny

Low. This is a one-line bug fix that brings the heap profiler into exact parity with the sibling CPU profiler — src/jsc/BunCPUProfiler.rs:185 already uses path.join(&[config.dir]) for the same purpose, so this is conforming to an established pattern rather than introducing new logic. Path::join resets the accumulated path on absolute segments while still resolving relative segments under CWD, which is the documented and desired semantics here.

Other factors

The new test mirrors the structure of the existing --heap-prof-dir tests (same harness helpers, spawn pattern, and assertion style) and additionally asserts nothing leaks under CWD. The bug-hunting system found no issues. No outstanding reviewer comments.

@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 — one-line appendjoin to match the CPU profiler, with a regression test.

Extended reasoning...

Overview

This PR changes a single functional line in src/jsc/BunHeapProfiler.rs: path.append(config.dir)? becomes path.join(&[config.dir])?, so that an absolute --heap-prof-dir replaces the CWD-rooted base path instead of being stripped of its leading separator and appended as a relative segment. A clarifying comment explains the append vs join semantics. The accompanying test in test/cli/heap-prof.test.ts spawns bun from one temp dir with --heap-prof-dir pointing at a second absolute temp dir and asserts the snapshot lands in the target and not under CWD.

Security risks

None. The flag already lets users choose where to write a heap snapshot; this change just makes absolute paths behave as documented instead of being silently relativized. There is no privilege boundary, no injection surface, and no new I/O — the write path is unchanged.

Level of scrutiny

Low. This is a targeted one-line bugfix in a developer-facing profiling utility (not hot-path runtime code), and it brings BunHeapProfiler.rs into exact alignment with the existing, working pattern in BunCPUProfiler.rs:185 (path.join(&[config.dir])). The semantics of Path::join resetting on absolute segments are well-established in src/paths/Path.rs, and the PR description correctly diagnoses the debug_assert!(!is_input_absolute(input)) misuse in append.

Other factors

  • The new test is well-constructed (separate CWD vs target dirs, positive assertion on target, negative recursive-glob assertion on CWD) and follows the same harness conventions as adjacent tests in the file.
  • The one inline nit (absolute --heap-prof-name still hits append) is pre-existing, shared with BunCPUProfiler.rs:189, and out of scope for this fix — non-blocking.
  • The single CI failure (next-pages/dev-server.test.ts on macOS aarch64) is unrelated to heap profiling or path resolution.
  • No prior human review comments are outstanding.

Comment thread src/jsc/BunHeapProfiler.rs
The profiler filename was appended with Path::append, which trims an
absolute input as relative (stripping the leading separator in release,
tripping a debug_assert in debug) the same way the directory did. Use
Path::join for the filename in both the heap and CPU profilers so an
absolute --heap-prof-name / --cpu-prof-name is honored and both profilers
stay consistent.
@robobun robobun changed the title Honor absolute --heap-prof-dir paths Honor absolute paths for heap and CPU profiler --*-dir/--*-name options Jun 21, 2026
@robobun

robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

Expanded the fix per the review: the same append-strips-absolute bug was one line below, on the filename (--heap-prof-name), and the CPU profiler had the identical pattern for --cpu-prof-name. Both profilers now use path.join for the directory and the filename, so absolute --heap-prof-dir / --heap-prof-name / --cpu-prof-name are all honored. Added absolute-name test cases alongside the absolute-dir one. Updated the title/description to match.

@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: 3

🤖 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 `@src/jsc/BunCPUProfiler.rs`:
- Around line 181-192: The issue is that the retry error handling path only
creates the config.dir directory, but when config.dir is empty and the filename
is an absolute path with non-existent parent directories, those parent
directories are never created, causing ENOENT to remain unrecoverable. When
handling the retry logic after the path.join calls fail (in the error handling
path not shown in the diff), ensure that if config.dir is empty, extract and
create the parent directory path from the absolute filename instead of skipping
directory creation entirely. This way, whether the absolute path comes from
config.dir or from the filename parameter, the necessary parent directories will
be created before attempting to write the profile.

In `@test/cli/heap-prof.test.ts`:
- Around line 176-185: Move the exitCode assertion to the end of the test.
Currently the expect(exitCode).toBe(0) assertion appears before the filesystem
side-effect validations. Relocate this assertion to be the last check in the
test, after the cwdFiles validation and all other assertions, following the
coding guideline that exit code checks should come last to ensure more specific
assertions appear first for better error reporting.

In `@test/cli/run/cpu-prof.test.ts`:
- Around line 151-159: The test currently only validates the case where the
absolute target directory exists, which misses regressions in parent directory
creation. Modify the test to use a non-existent absolute parent directory for
the target path (e.g., create a path with a parent that doesn't exist yet), and
reorder the assertions so that the filesystem validation checks on
Bun.file(target).size and the cwdFiles array come before the final
expect(exitCode).toBe(0) assertion, since subprocess tests should assert
filesystem output first and exit code last.
🪄 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: 2eca6b29-599b-4e8a-ade9-02c70df89692

📥 Commits

Reviewing files that changed from the base of the PR and between 3c00307 and 66cadce.

📒 Files selected for processing (4)
  • src/jsc/BunCPUProfiler.rs
  • src/jsc/BunHeapProfiler.rs
  • test/cli/heap-prof.test.ts
  • test/cli/run/cpu-prof.test.ts

Comment thread src/jsc/BunCPUProfiler.rs
Comment thread test/cli/heap-prof.test.ts
Comment thread test/cli/run/cpu-prof.test.ts
The CPU profiler write-retry only created config.dir, so an absolute
--cpu-prof-name pointing at a non-existent parent failed with ENOENT even
after the join fix. Derive the directory from the final output path (like
the heap profiler) so the parent is created before retrying. Strengthen
the profiler-name tests to use a non-existent absolute parent and assert
the exit code last.

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

Additional findings (outside current diff — PR may have been updated during review):

  • 🟡 src/jsc/BunCPUProfiler.rs:192 — The ENOENT retry path here still creates config.dir (and skips the retry entirely when config.dir is empty), but with this PR an absolute --cpu-prof-name resets the output path via join, so the directory that actually needs to exist is dirname(output_path), not config.dir. bun --cpu-prof --cpu-prof-name /not/yet/foo.cpuprofile will fail with WriteFailed instead of creating /not/yet. BunHeapProfiler.rs already does this right via resolve_path::dirname(path_buf.slice()) — worth mirroring that here while you're touching this file.

    Extended reasoning...

    What

    write_profile_to_file in BunCPUProfiler.rs has an ENOENT/EPERM/EACCES retry path that creates the output directory before retrying the write. That retry is gated on if !config.dir.is_empty() and creates config.dir via Fd::cwd().make_path(config.dir). Before this PR that was fine: config.name was effectively a bare filename appended under config.dir, so config.dir was always the directory that needed creating.

    This PR changes build_output_path to call path.join(&[filename]) so that an absolute --cpu-prof-name is honored — join resets the accumulated path when the segment is absolute. After the reset, the actual output directory is dirname(--cpu-prof-name), which has nothing to do with config.dir. The retry path was not updated to match.

    Step-by-step

    Take the case the PR explicitly intends to support — an absolute --cpu-prof-name with no --cpu-prof-dir:

    1. bun --cpu-prof --cpu-prof-name /not/yet/foo.cpuprofile script.js (where /not/yet does not exist).
    2. config.name = b"/not/yet/foo.cpuprofile", config.dir = b"".
    3. build_output_path: path starts at CWD, config.dir is empty so the dir-join is skipped, then path.join(&[b"/not/yet/foo.cpuprofile"]) resets path to /not/yet/foo.cpuprofile.
    4. First write_file_os_path(Fd::cwd(), "/not/yet/foo.cpuprofile", ...)ENOENT.
    5. Retry branch: errno == ENOENT matches, but config.dir.is_empty() is true → falls into the else { return Err(ProfilerError::WriteFailed); } arm. No make_path is attempted for /not/yet.

    A second variant makes the divergence more visible:

    1. bun --cpu-prof --cpu-prof-dir relative/profiles --cpu-prof-name /abs/target/foo.cpuprofile script.js.
    2. build_output_path: path.join(&["relative/profiles"])$CWD/relative/profiles, then path.join(&["/abs/target/foo.cpuprofile"]) resets to /abs/target/foo.cpuprofile.
    3. First write → ENOENT.
    4. Retry: config.dir = "relative/profiles" is non-empty, so Fd::cwd().make_path("relative/profiles") creates $CWD/relative/profiles — the wrong directory.
    5. Retry write to /abs/target/foo.cpuprofile still hits ENOENTWriteFailed, plus a stray empty directory left under CWD.

    Why nothing prevents it

    The retry logic predates this PR and assumed config.dir was always the parent of the output file. The PR breaks that invariant by letting --cpu-prof-name reset the path, but only updated build_output_path, not the retry. The new test in cpu-prof.test.ts doesn't catch this because it pre-creates targetDir via tempDir("cpu-prof-name-abs-target", {}), so the first write succeeds and the retry path is never exercised.

    Sibling already does it right

    BunHeapProfiler.rs (lines ~67–69) handles the identical situation by deriving the directory from the resolved output path:

    let dir_path = resolve_path::dirname::<bun_paths::platform::Auto>(path_buf.slice());
    if !dir_path.is_empty() {
        let _ = Fd::cwd().make_path(dir_path);
        ...
    }

    That works regardless of whether the final path came from --heap-prof-dir, --heap-prof-name, or both.

    Impact

    Edge case (absolute --cpu-prof-name pointing into a not-yet-existing directory) and the failure is a clean WriteFailed rather than a crash or silent corruption, so non-blocking. But the PR's stated goal is to make absolute names a supported input for both profilers, the sibling file already shows the correct pattern, and CLAUDE.md's "fix the whole class in the same PR" applies — the CPU profiler's retry should mirror the heap profiler's dirname(path_buf.slice()) approach.

@robobun

robobun commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator Author

@claude the CPU profiler retry is already handled in 6a9a9a5: write_profile_to_file now derives the parent from the resolved output path (resolve_path::dirname(path_buf.slice())) and creates it before retrying, mirroring the heap profiler. The --cpu-prof-name test was also updated to target a non-existent absolute parent so the retry path is exercised.

@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 filename join and CPU-profiler retry-dirname follow-ups address everything raised; thanks.

Extended reasoning...

Overview

This PR fixes #32568: absolute paths passed to --heap-prof-dir, --heap-prof-name, and --cpu-prof-name were being treated as relative (leading separator stripped) because Path::append trims its input as relative when the accumulated path is already rooted. The fix swaps append for join (which resets on absolute segments) in build_output_path for both BunHeapProfiler.rs and BunCPUProfiler.rs, and aligns the CPU profiler's ENOENT-retry directory creation with the heap profiler's by deriving the parent via resolve_path::dirname on the final output path instead of only config.dir. Three new tests in heap-prof.test.ts and cpu-prof.test.ts cover absolute --heap-prof-dir, absolute --heap-prof-name (with non-existent parent), and absolute --cpu-prof-name (with non-existent parent).

Security risks

None. This affects only where the user's own profiler output file is written, to a path the user explicitly passed on the CLI. No new input parsing, no privilege boundaries crossed.

Level of scrutiny

Low–medium. The core change is a one-method swap (appendjoin) whose semantics are documented inline and already used for --cpu-prof-dir before this PR. The retry-dir change mirrors the existing heap-profiler code path verbatim. The non-Windows slice_z() re-derivation is a mechanical borrow-checker accommodation (slice_z takes &mut self, conflicting with the new immutable path_buf.slice() borrow for dirname).

Other factors

I left one inline comment on an earlier revision (the filename append had the same bug) — addressed in 66cadce. CodeRabbit raised the retry-dir gap and two test-ordering nits — all addressed in 6a9a9a5 and confirmed resolved. The bug-hunting system found no issues on the current revision. New tests follow the existing patterns in their files (same tempDir/Bun.spawn/glob-scan structure) and assert the regression directly (file lands at absolute target, nothing under CWD). No CODEOWNERS coverage for these paths.

Jarred-Sumner pushed a commit that referenced this pull request Aug 4, 2026
…ir instead of panicking (#36881)

### What

`--cpu-prof-dir`, `--cpu-prof-name`, `--heap-prof-dir`, or
`--heap-prof-name` with a value whose length pushes the output path past
`PATH_MAX` crashed the whole process at exit and lost the profile:

```
panic: range end index 4106 out of range for slice of length 4095
panic: index out of bounds: the len is 4096 but the index is 4096
oh no: Bun has crashed. This indicates a bug in Bun, not your code.
```

A value that is merely too long for the filesystem but still fits the
buffer (e.g. 3000 bytes on Linux) already produced the `WriteFailed:
Failed to write CPU profile` warning and exited cleanly; the panic only
fired at the `PATH_MAX` boundary.

### Repro

```sh
bun --cpu-prof --cpu-prof-dir="$(printf 'd%.0s' {1..4085})" -e 'const e=Date.now()+60; while(Date.now()<e);'
```

### Cause

`build_output_path` in both `src/jsc/BunCPUProfiler.rs` and
`src/jsc/BunHeapProfiler.rs` writes the dir and filename into an
`AutoAbsPath`, which is `CheckLength::ASSUME`: over-long input does not
return `Err(MaxPathExceeded)` but panics inside `PooledBuf::append`
slice indexing. The in-code `// Err arm is unreachable` annotation
assumed the inputs were bounded, but both `config.dir` and `config.name`
are raw CLI bytes.

### Fix

Add a public `bun_paths::AutoAbsPathChecked` alias
(`CheckLength::CheckForGreaterThanMaxPath`) and make `Path::join` honor
`CheckLength` by routing through `join_abs_string_buf_checked` in that
mode, so `from`/`append`/`append_fmt`/`join` all return
`Err(MaxPathExceeded)` on overflow. `ASSUME`-mode callers (the default,
and all existing users) are unchanged.

Both profilers switch to `AutoAbsPathChecked` and propagate the error
with `?`, dropping the `.expect("unreachable")` unwraps. The overflow
surfaces as the existing `FilenameTooLong: Failed to write CPU profile`
/ `MaxPathExceeded: Failed to write heap profile` warning and the
process exits cleanly.

### Verification

New parameterized cases in `test/cli/run/cpu-prof.test.ts` and
`test/cli/heap-prof.test.ts` pass 5000-byte `--*-prof-dir` /
`--*-prof-name` values, plus a 2500+2500 combined case, and assert the
warning text, `exitCode === 0`, and `signalCode === null`. They fail
before (SIGABRT with the panic above) and pass after. Skipped on
Windows, where the path buffer is ~98 KB and the `CreateProcess`
command-line limit (~32 KB) prevents delivering an overflowing argument.

The `--cpu-prof-dir=inner --cpu-prof-name=/abs/x` debug-assert case
(absolute name appended onto a rooted path) is already covered by
#32570, which switches `append` to `join` for the filename; this PR is
only about the `PATH_MAX` overflow.

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 1 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/cli/heap-prof.test.ts test/cli/run/cpu-prof.test.ts

<!-- robobun:evidence:end -->
springmin pushed a commit to springmin/bun that referenced this pull request Aug 5, 2026
…ir instead of panicking (oven-sh#36881)

### What

`--cpu-prof-dir`, `--cpu-prof-name`, `--heap-prof-dir`, or
`--heap-prof-name` with a value whose length pushes the output path past
`PATH_MAX` crashed the whole process at exit and lost the profile:

```
panic: range end index 4106 out of range for slice of length 4095
panic: index out of bounds: the len is 4096 but the index is 4096
oh no: Bun has crashed. This indicates a bug in Bun, not your code.
```

A value that is merely too long for the filesystem but still fits the
buffer (e.g. 3000 bytes on Linux) already produced the `WriteFailed:
Failed to write CPU profile` warning and exited cleanly; the panic only
fired at the `PATH_MAX` boundary.

### Repro

```sh
bun --cpu-prof --cpu-prof-dir="$(printf 'd%.0s' {1..4085})" -e 'const e=Date.now()+60; while(Date.now()<e);'
```

### Cause

`build_output_path` in both `src/jsc/BunCPUProfiler.rs` and
`src/jsc/BunHeapProfiler.rs` writes the dir and filename into an
`AutoAbsPath`, which is `CheckLength::ASSUME`: over-long input does not
return `Err(MaxPathExceeded)` but panics inside `PooledBuf::append`
slice indexing. The in-code `// Err arm is unreachable` annotation
assumed the inputs were bounded, but both `config.dir` and `config.name`
are raw CLI bytes.

### Fix

Add a public `bun_paths::AutoAbsPathChecked` alias
(`CheckLength::CheckForGreaterThanMaxPath`) and make `Path::join` honor
`CheckLength` by routing through `join_abs_string_buf_checked` in that
mode, so `from`/`append`/`append_fmt`/`join` all return
`Err(MaxPathExceeded)` on overflow. `ASSUME`-mode callers (the default,
and all existing users) are unchanged.

Both profilers switch to `AutoAbsPathChecked` and propagate the error
with `?`, dropping the `.expect("unreachable")` unwraps. The overflow
surfaces as the existing `FilenameTooLong: Failed to write CPU profile`
/ `MaxPathExceeded: Failed to write heap profile` warning and the
process exits cleanly.

### Verification

New parameterized cases in `test/cli/run/cpu-prof.test.ts` and
`test/cli/heap-prof.test.ts` pass 5000-byte `--*-prof-dir` /
`--*-prof-name` values, plus a 2500+2500 combined case, and assert the
warning text, `exitCode === 0`, and `signalCode === null`. They fail
before (SIGABRT with the panic above) and pass after. Skipped on
Windows, where the path buffer is ~98 KB and the `CreateProcess`
command-line limit (~32 KB) prevents delivering an overflowing argument.

The `--cpu-prof-dir=inner --cpu-prof-name=/abs/x` debug-assert case
(absolute name appended onto a rooted path) is already covered by
oven-sh#32570, which switches `append` to `join` for the filename; this PR is
only about the `PATH_MAX` overflow.

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 1 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/cli/heap-prof.test.ts test/cli/run/cpu-prof.test.ts

<!-- robobun:evidence:end -->
@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Triage note after #32568 was closed. Checked against a debug build of main (165dc9f) and the 1.4.0 canary (da3851e):

Three of the four changes here landed in #34660, which switched both profilers to Path::join: an absolute --heap-prof-dir, an absolute --heap-prof-name (including creating a missing parent directory), and an absolute --cpu-prof-name pointing into an existing directory all work on main now. The heap profiler also derives the directory it creates from the final output path, so its retry covers an absolute name.

The CPU profiler's retry still does make_path(config.dir) (src/jsc/BunCPUProfiler.rs:131), so the write_profile_to_file hunk here is still needed: bun --cpu-prof --cpu-prof-name /abs/missing-dir/x.cpuprofile prints WriteFailed: Failed to write CPU profile and writes nothing, on both builds. That is what the cpu-prof.test.ts case from this PR fails on against main.

The two heap tests from this PR also fail against main, but only because of unrelated drift since June: the default heap profile is now written with a .heapprofile extension, and "Heap profile written to:" is only printed for the markdown format.

Leaving this open; it should be rebased down to the write_profile_to_file hunk in BunCPUProfiler.rs plus the --cpu-prof-name test, and it should no longer claim to fix #32568.

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.

--heap-prof-dir mishandles absolute paths

1 participant