Skip to content

install: format versions with the string buffer they were parsed from (bun outdated table, minimum-release-age messages) - #38649

Open
robobun wants to merge 1 commit into
mainfrom
farm/a55aaa50/outdated-version-string-buf
Open

robobun wants to merge 1 commit into
mainfrom
farm/a55aaa50/outdated-version-string-buf

Conversation

@robobun

@robobun robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • bun outdated --minimum-release-age (or with install.minimumReleaseAge configured) prints a misaligned table when the installed version has a prerelease tag longer than 8 bytes (1.0.0-snapshot.20240101) and nothing passes the filter: the Update/Latest header cells are sized for 1.0.0- * while the row prints 1.0.0-snapshot.20240101 *, so the row overflows the table.
  • Cause: the column width pass in src/runtime/cli/outdated_command.rs:476 and :490 formats current_version, which comes from the lockfile, against manifest.string_buf. The print pass (:696, :727) and the Current column (:465) use the lockfile buffer. Reading the tag out of the wrong buffer returns an empty slice once the offset is past the end of the manifest buffer (or unrelated bytes before that), so the width is computed from the wrong text.
  • The same mistake exists in two other minimum-release-age messages:
    • src/install/PackageManager/PackageManagerEnqueue.rs:2501: the --verbose line [minimum-release-age] pkg@<range> selected X instead of Y formats the dependency's range (lockfile) against the manifest buffer. Observed: nightly-package@>=1.0.0-ightly.20240101h <2.0.0.
    • src/install/PackageManager/PackageManagerEnqueue.rs:1085: Version "pkg@<version>" was published within minimum release age of N seconds formats the manifest's version against the lockfile buffer. Observed: Version "nightly-package@1.0.0-htly.20240101.tg".
  • All three were like this in the Zig code as well; this is not a port regression.

Fix

  • Pass each value the buffer it was parsed from: the lockfile buffer for the current version and the dependency range, the manifest's string_buf for the manifest's version. No other logic changes.
  • Correct because these are the buffers the same values are already formatted with a few lines away (outdated_command.rs:465/696/727, and PackageManagerEnqueue.rs:2465-2467 passes the lockfile buffer for the same range when matching), so the fixed sites now agree with their neighbours.
  • Tests: test/cli/install/minimum-release-age.test.ts, new describe("prerelease tags longer than an inline semver string"), one test per site. All three fail on the unfixed binary (misaligned table, >=1.0.0-ightly.20240101h, 1.0.0-htly.20240101.tg) and pass with the fix; the whole file passes (52 tests) with the debug build.
    • The bun outdated test installs three up-to-date packages next to snapshot-package so that the prerelease tag's lockfile offset lies past the end of snapshot-package's manifest buffer; with a nearly empty lockfile the wrong buffer yields wrong bytes of the right length and the table lines up by accident. The test asserts the exact table.
    • The cached-manifest test runs the second install with BUN_MANIFEST_CACHE=1, which makes bun treat the cached manifest as stale (the state any manifest is in five minutes after it was fetched) and take the exact-pin-from-cache path that prints the message.

Background

  • Semver values (Version, prerelease/build tags, Group ranges) do not own their text. Strings of up to 8 bytes are stored inline; longer ones are an offset and length into the string buffer they were parsed from, and every .fmt(buf) / .slice(buf) call has to be given that buffer.
  • There are two buffers in these code paths: the lockfile's buffers.string_bytes (everything read from bun.lock/package.json: installed versions, dependency ranges) and each package manifest's string_buf (everything read from the registry: candidate versions). slice is bounds-checked, so a wrong buffer produces garbage or an empty string rather than a crash, which is why this only shows up as misaligned or mangled output.
  • minimum-release-age is what makes the bun outdated fallback branches reachable: without it, latest always resolves and update only fails when the installed version was unpublished.
Related sites found while grepping for the same pattern, intentionally not in this PR

These have different triggers and need their own fixtures, so they are being tracked separately rather than bundled here:

  • src/semver/Version.rs:845: DiffFormatter prints its own build tag with other_buf when colours are on and the other version has no build tag (affects bun outdated with build metadata in the registry version).
  • src/runtime/cli/pm_view_command.rs:213: bun pm view / bun info passes the manifest buffer as the buffer for a range parsed from the CLI argument.
  • Auto-install's disk-cache resolution (PackageManagerResolution.rs:152-202, Tag::clone_into) records tag offsets relative to a sub-slice of tags_buf and later slices against the whole buffer.
  • While reproducing the PackageManagerEnqueue.rs:1085 message: the exact-pin-from-cached-manifest path does not check that the cached manifest actually has publish times, so an abbreviated manifest cached by an earlier install lets an exact pin bypass --minimum-release-age. Handed off separately as its own bug.

Semver strings longer than 8 bytes are offsets into the buffer they were
parsed from. Three minimum-release-age code paths formatted a version
against the other side's buffer:

- bun outdated's column width pass formatted the lockfile's current
  version against the manifest buffer when no update/latest candidate
  passed the filter, so the Update and Latest columns were sized for the
  wrong text and the row overflowed the table.
- the --verbose "[minimum-release-age] ... selected ..." line formatted
  the dependency's range (lockfile) against the manifest buffer.
- the "was published within minimum release age" error formatted the
  manifest's version against the lockfile buffer.
@coderabbitai

coderabbitai Bot commented Aug 14, 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 seconds

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: 92856cad-4198-4c35-9317-2045c1197d31

📥 Commits

Reviewing files that changed from the base of the PR and between 97a4363 and 547b66b.

📒 Files selected for processing (3)
  • src/install/PackageManager/PackageManagerEnqueue.rs
  • src/runtime/cli/outdated_command.rs
  • test/cli/install/minimum-release-age.test.ts

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

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Reproduced all three on the unfixed binary with the mock registry from test/cli/install/minimum-release-age.test.ts:

  • bun outdated --minimum-release-age 432000 with snapshot-package@1.0.0-snapshot.20240101 installed through its snapshot dist-tag: the Update/Latest columns are sized at 8 characters while the row prints 1.0.0-snapshot.20240101 *, so the row is 100 characters wide against a 68 character table.
  • bun install --minimum-release-age 432000 --verbose with "nightly-package": "^1.0.0-nightly.20240101": [minimum-release-age] nightly-package@>=1.0.0-ightly.20240101h <2.0.0 selected ...
  • the same project re-pinned to 1.0.0-nightly.20240102 and installed again with the manifest already cached: error: Version "nightly-package@1.0.0-htly.20240101.tg" was published within minimum release age of 432000 seconds

The three new tests in that file fail the same way on the unfixed binary and pass with the fix; the whole file passes with a debug build.

@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 — each of the four .fmt(buf) sites now receives the buffer the value was actually parsed from, matching how the same values are already formatted a few lines away.

What was reviewed:

  • outdated_command.rs:476/490: current_version is resolution.npm().version from the lockfile; the print pass at :696/:727 and the Current column at :465 already use string_buf (lockfile), so the width pass now agrees.
  • PackageManagerEnqueue.rs:1085: find_result comes from loaded_manifest.find_by_version(...), so the manifest's string_buf is correct; the .unwrap() is already established at :1053.
  • PackageManagerEnqueue.rs:2501: version is the dependency range from the lockfile — the same value is passed with this.lockfile.buffers.string_bytes at :2467 for matching; result.version/newest on the adjacent lines still correctly use manifest_buf.
  • Tests: three new cases, one per site, with fixtures whose prerelease tags exceed the 8-byte inline limit; hermetic against the existing mock registry.
Extended reasoning...

Overview

Four one-line buffer-argument fixes across two files: the bun outdated column-width pass (outdated_command.rs:476/490) and two minimum-release-age messages in PackageManagerEnqueue.rs (:1085, :2501). Each site was formatting a semver Version/Group against the wrong string buffer (manifest vs lockfile), which mangles prerelease/build tags longer than the 8-byte inline threshold. Three regression tests are added to test/cli/install/minimum-release-age.test.ts plus two mock-registry packages with long prerelease tags.

Security risks

None. The changes affect only display output (table column widths, a --verbose diagnostic, and an error message). No parsing, resolution, or install-path logic is touched; the values themselves are unchanged, only which backing buffer their tag text is sliced from when printed.

Level of scrutiny

Low. Each fix is a mechanical buffer-argument swap whose correctness is directly verifiable from the value's provenance a few lines above, and each fixed site has a neighbouring line that already formats the same value with the now-chosen buffer (:465/696/727 for current_version, :2467 for the dependency range, :1053 for find_result's manifest). There are no control-flow, ownership, or allocation changes.

Other factors

The PR description explains the inline-vs-offset semver string representation and why the wrong buffer produces garbage rather than a crash, and explicitly lists related same-pattern sites that are intentionally deferred to separate PRs with their own fixtures. The new tests follow the file's existing conventions (mock registry, tempDir, concurrent pipe drain, bunEnv spread), assert exact output, and the outdated-table test also asserts uniform row widths as a structural check independent of the snapshot. No CODEOWNERS apply to the touched paths, and there are no prior human review comments.

This branch has not been deployed

No deployments
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