Skip to content

fix(BunFile): lastModified returns 0 for missing files instead of 2^52-1 sentinel - #33652

Closed
robobun wants to merge 2 commits into
mainfrom
farm/40a4c942/fix-lastmodified-sentinel
Closed

robobun wants to merge 2 commits into
mainfrom
farm/40a4c942/fix-lastmodified-sentinel

Conversation

@robobun

@robobun robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Repro

const m = Bun.file("/definitely/not/a/real/path");
console.log(m.lastModified);                 // 4503599627370495  (2**52 - 1)
console.log(new Date(m.lastModified).toISOString()); // +144683-05-23T16:29:30.495Z
console.log(m.size, await m.exists());       // 0 false  (these two are already normalized)

await Bun.write("/tmp/real", "x");
console.log(m.lastModified > Bun.file("/tmp/real").lastModified); // true: missing is "newer"

Cause

get_last_modified reads the file store's last_modified field, which is initialized to jsc::INIT_TIMESTAMP ((1u64 << 52) - 1). When the field is still the sentinel it calls resolve_file_stat, but that function silently does nothing on stat failure (ENOENT etc.), so the sentinel is returned to JS unchanged. The fallthrough for in-memory Blobs also returned the raw sentinel.

.size already normalizes this case to 0 via resolve_size; .lastModified was the one getter that leaked the lazy marker.

Fix

After the resolve attempt, if the field is still INIT_TIMESTAMP, return 0 instead of the sentinel. Same for the in-memory Blob fallthrough. new File([...], name) is unchanged and still returns Date.now() when no lastModified option is passed, per the File API.

Verification

USE_SYSTEM_BUN=1 bun test test/js/bun/util/bun-file.test.ts -t lastModified   # 4 fail, all show 4503599627370495
bun bd test test/js/bun/util/bun-file.test.ts                                 # 10 pass

…ernal sentinel

Bun.file(missingPath).lastModified returned 4503599627370495 (2^52-1, the
INIT_TIMESTAMP sentinel used to mark 'not yet statted'). resolve_file_stat
silently no-ops when stat fails, so the sentinel flowed straight to JS. A
missing file thus compared newer than every real file.

The getter now normalizes the sentinel to 0, matching .size which already
reports 0 for a missing file. Plain in-memory Blob.lastModified is also 0
now instead of the sentinel. new File() remains unchanged (Date.now() per
the File spec).
@coderabbitai

coderabbitai Bot commented Jul 7, 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: 9 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: b406093a-d8a0-4205-95f3-9ad395cc61d9

📥 Commits

Reviewing files that changed from the base of the PR and between 3f5d816 and fa12f28.

📒 Files selected for processing (2)
  • src/runtime/webcore/Blob.rs
  • test/js/bun/util/bun-file.test.ts

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

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

robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:53 PM PT - Jul 7th, 2026

✅ @robobun, your commit fa12f28959d56ed136bbef3c3f603a78387b30d7 passed in Build #70017! 🎉


🧪   To try this PR locally:

bunx bun-pr 33652

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

bun-33652 --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 — straightforward sentinel normalization mirroring the existing .size behavior.

Extended reasoning...

Overview

This PR fixes Bun.file().lastModified to return 0 instead of the internal lazy-stat sentinel 2**52 - 1 (jsc::INIT_TIMESTAMP) when the underlying file does not exist or stat fails. It touches one getter in src/runtime/webcore/Blob.rs (get_last_modified) and adds four tests to test/js/bun/util/bun-file.test.ts. The fallthrough for in-memory Blobs (non-File) is also changed from the sentinel to 0.

Security risks

None. This is a read-only getter returning a numeric timestamp; no auth, crypto, path handling, or untrusted-input parsing is involved. The change only substitutes one constant return value for another on a path that was already reachable.

Level of scrutiny

Low-to-medium. The diff is ~10 lines of native code plus tests. The fix is a direct analogue of the existing .size → 0 normalization for missing files (resolve_size), so the design decision is already established in the codebase. The S3 branch (which skips resolve_file_stat) now also returns 0 instead of the sentinel when unstatted, which is strictly less surprising than 4503599627370495. The is_jsdom_file path (new File(...)) is unchanged and still returns the stored self.last_modified, so the File API's Date.now() default is preserved — I checked existing structured-clone and fetch tests that assert file.lastModified > 0 and they remain valid.

Other factors

  • Tests cover the missing-path case, the deleted-after-creation case, ordering vs. a real file, and the in-memory Blob fallthrough.
  • PR description shows the tests fail under USE_SYSTEM_BUN=1 and pass under bun bd, satisfying the repo's test-validity requirement.
  • No CODEOWNERS entries cover these paths.
  • The bug-hunting system found no issues.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Allow assigning to Blob#lastModified #32425 - Also fixes the INIT_TIMESTAMP sentinel leaking from get_last_modified in Blob.rs; that PR's primary goal is making Blob#lastModified writable, but it incidentally applies the same sentinel-to-zero fix in the same code area

🤖 Generated with Claude Code

@robobun

robobun commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Not a duplicate of #32425. That PR makes Blob#lastModified writable (issue #14257) and only touches the fallthrough at the end of get_last_modified. It does not touch the file-backed branch, so Bun.file("/missing").lastModified still returns 4503599627370495 under that change (the file store's last_modified field stays at INIT_TIMESTAMP when resolve_file_stat fails, and that value is returned directly).

This PR normalizes the sentinel in the file-backed branch, which is the bug reported here. The only overlap is the final fallthrough line; whichever lands second is a trivial rebase.

@robobun

robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI status: this diff is green on every lane that executed tests.

  • Build 69869: 75 checks passed (all linux/windows/freebsd/android). darwin-14 aarch64 failed on test/integration/next-pages/test/dev-server.test.ts (puppeteer Chromium DevTools WebSocket "Connection ended" after 3 retries) and test/js/bun/webview/webview.test.ts ("WebView host process killed by signal 5"), both external-browser-process issues unrelated to this change. The other darwin aarch64 attempt died at failed to git clone vendor 'elysia': timeout before running any tests.
  • Build 70017 (retrigger): same darwin agent availability problem, jobs expiring in queue.

test/js/bun/util/bun-file.test.ts (the new tests) passed on every platform that ran it. The diff is a 10-line getter normalization in Blob.rs and cannot affect puppeteer WebSocket or WKWebView host processes. Ready for maintainer review.

@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 — straightforward sentinel normalization matching how .size already handles missing files.

Extended reasoning...

Overview

Two-line fix in src/runtime/webcore/Blob.rs get_last_modified: after resolve_file_stat fails to populate last_modified (ENOENT etc.), return 0 instead of leaking the internal jsc::INIT_TIMESTAMP sentinel (2**52 - 1) to JS. The fallthrough for in-memory Blob is normalized the same way. Four new tests in test/js/bun/util/bun-file.test.ts cover missing path, comparison against a real file, post-deletion, and in-memory Blob.

Security risks

None. This is a read-only getter returning a numeric timestamp; no input parsing, no allocation, no I/O added. The change only maps one sentinel constant to 0 at the JS boundary.

Level of scrutiny

Low. The fix is a two-branch value substitution in a getter, directly mirroring the existing pattern where .size already normalizes to 0 for missing files via resolve_size. The is_jsdom_file() branch (which handles new File(...) per the File API spec) is untouched, so spec-mandated Date.now() defaults for File are preserved. The S3 branch also now sees 0 instead of the sentinel when unresolved, which is strictly an improvement.

Other factors

  • Tests follow harness conventions (tempDir with using, no hardcoded paths, assert exact values), and the PR description confirms they fail under USE_SYSTEM_BUN=1 and pass under the debug build.
  • CI is green on all platforms that ran; the only red checks are expired darwin agent slots, not test failures.
  • No outstanding reviewer comments; the duplicate-PR bot flag was addressed by the author (the overlap with #32425 is one line and non-conflicting in intent).

@robobun

robobun commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator Author

The lastModified sentinel fix here has been folded into #33659 (cdead2d) since that PR already reworks get_last_modified / resolve_file_stat for the same stat-cache root. The four tests from this PR are included there under the BunFile exists()/size/lastModified describe. This PR can be closed once #33659 lands.

@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-07 and it conflicts with main. 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