Skip to content

bunfig: fall back to $HOME/.bunfig.toml when $XDG_CONFIG_HOME has none - #36486

Closed
Properrr wants to merge 2 commits into
oven-sh:mainfrom
Properrr:claude/xdg-npmrc-home-fallback
Closed

bunfig: fall back to $HOME/.bunfig.toml when $XDG_CONFIG_HOME has none#36486
Properrr wants to merge 2 commits into
oven-sh:mainfrom
Properrr:claude/xdg-npmrc-home-fallback

Conversation

@Properrr

@Properrr Properrr commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #23128 (the .bunfig.toml half; the .npmrc half landed in #36289).

Rebased onto main by @robobun at a maintainer's request: #36289 already changed the PackageManager.rs hunk this PR used to touch, so this PR now only carries the .bunfig.toml side and uses the same rule as #36289. The original description is kept at the bottom.

Problem

  • With XDG_CONFIG_HOME exported (GitHub Actions ubuntu runners and most Linux desktops export it), ~/.bunfig.toml is never read by bun install, bun add, bun pm ... and the other commands that load the global bunfig, whether or not $XDG_CONFIG_HOME/.bunfig.toml exists.
  • Cause: get_home_config_path in src/bunfig/arguments.rs returned $XDG_CONFIG_HOME/.bunfig.toml as soon as the variable was set, without checking that the file exists. XDG_CONFIG_HOME="" had the same effect, since the empty value still counted as set.
  • docs/runtime/bunfig.mdx and docs/pm/cli/install.mdx describe the global file as $XDG_CONFIG_HOME/.bunfig.toml or $HOME/.bunfig.toml, and .npmrc has used that rule since install: fall back to $HOME/.npmrc when $XDG_CONFIG_HOME is set #36289.

Fix

  • get_home_config_path uses $XDG_CONFIG_HOME/.bunfig.toml only when that file exists (bun_sys::exists_z), otherwise $HOME/.bunfig.toml. Both variables are read with get_not_empty(), so an empty string counts as unset. Still at most one global file is loaded, and a bunfig that was deliberately placed under XDG_CONFIG_HOME keeps winning.
  • PackageManager.rs is untouched; the user-level .npmrc lookup there already works this way after install: fall back to $HOME/.npmrc when $XDG_CONFIG_HOME is set #36289.
  • Tests: test/cli/install/npmrc.test.ts, describe("global .bunfig.toml lookup"), next to the user .npmrc lookup cases from install: fall back to $HOME/.npmrc when $XDG_CONFIG_HOME is set #36289. Each case points the candidate files at differently named cache directories and reads back which one bun pm cache prints (offline, no registry involved):
    • $XDG_CONFIG_HOME/.bunfig.toml present: it wins
    • XDG_CONFIG_HOME set, no file there: $HOME/.bunfig.toml is used (fails on main)
    • XDG_CONFIG_HOME unset: $HOME/.bunfig.toml is used
    • XDG_CONFIG_HOME="": $HOME/.bunfig.toml is used (fails on main)
  • USE_SYSTEM_BUN=1 bun test test/cli/install/npmrc.test.ts -t "bunfig.toml lookup" (bun 1.4.0): 2 pass, 2 fail, both failures printing the default cache directory instead of the one from $HOME/.bunfig.toml
  • bun bd test test/cli/install/npmrc.test.ts: 35 pass (whole file)
  • bun bd test test/cli/install/minimum-release-age.test.ts -t "global bunfig": 2 pass (existing tests that set XDG_CONFIG_HOME to a directory containing a .bunfig.toml)

Background

  • The global bunfig is the per-user .bunfig.toml that install-family commands (and bunx) load before the project's ./bunfig.toml; the local file's settings override it. Runtime commands such as bun run do not load it; that is unchanged here (bunfig: load global ~/.bunfig.toml for runtime commands; fall back past $XDG_CONFIG_HOME #34987 proposed changing it and is closed in favour of this PR).
  • XDG_CONFIG_HOME is the freedesktop variable for a user's config directory, usually ~/.config. Bun also looks for .bunfig.toml directly inside it, so on a machine that merely exports the variable the lookup used to stop at a path where nothing exists.
  • bun pm cache prints the install cache directory, which [install] cache = "..." in any loaded bunfig overrides, which is why the tests use it to see which file was read. BUN_INSTALL_CACHE_DIR would take precedence over bunfig and CI exports it, so the tests drop it from the environment.
Original description (before the rebase)

What does this PR do?

Fixes #23128.

bun install resolved its user-level .npmrc with:

XDG_CONFIG_HOME.get().or_else(|| HOME.get())

That picks one directory or the other. Because many Linux distros and shells export XDG_CONFIG_HOME by default, users who never opted into XDG would have $HOME/.npmrc silently ignored — scoped registries and auth tokens dropped with no diagnostic. $HOME/.bunfig.toml had the identical bug in get_home_config_path.

This probes $XDG_CONFIG_HOME/<name> and only uses it when the file is actually there, otherwise falling back to $HOME/<name>.

Two notes on the approach:

  • Fallback, not merge. Only one user-level file is ever loaded, which keeps npm's userconfig model and avoids introducing a new config layer with new precedence rules to reason about.
  • XDG still wins when populated, so anyone who deliberately moved their config there is unaffected. There's a test pinning that direction so it can't silently regress.

The resolution is shared between .npmrc and .bunfig.toml via bun_bunfig::arguments::user_config_path, since both sites had the same bug. bun_install already depends on bun_bunfig, so no new edges. Cost is one extra stat at startup, and only when both env vars are set.

How did you verify your code works?

Two tests added to test/cli/install/npmrc.test.ts. Both assert offline via bun pm cache reading cache= out of the resolved .npmrc, so there's no registry or network dependency:

  • falls back to $HOME/.npmrc when $XDG_CONFIG_HOME has none
  • prefers $XDG_CONFIG_HOME/.npmrc over $HOME/.npmrc

USE_SYSTEM_BUN=1 can't validate this file — it fails at import on bun:internal-for-testing, which release builds don't ship. So I verified the tests are real by reverting the source change, rebuilding, and re-running:

(fail) npmrc > falls back to $HOME/.npmrc when $XDG_CONFIG_HOME has none
 1 pass  1 fail

The fallback test fails without the fix; the precedence test passes without it, confirming it guards existing behavior rather than the new path. With the fix restored:

bun bd test test/cli/install/npmrc.test.ts \
            test/config/bunfig/bunfig-errors.test.ts \
            test/cli/bunfig-test-options.test.ts \
            test/cli/install/bun-run-bunfig.test.ts
 65 pass  0 fail

The bunfig suites are included because the shared helper also governs .bunfig.toml resolution. cargo clippy -p bun_bunfig -p bun_install --no-deps is clean.

Not covered: I only exercised this on Linux. The code path is platform-independent, and the tests now set USERPROFILE alongside HOME since env_var::HOME reads USERPROFILE on Windows — but I have not run them on macOS or Windows.

Correction to an earlier version of this description: I originally wrote that HOME is unset on Windows so the (None, ...) arms preserve existing behavior there. That was wrong — env_var::HOME is defined as posix = "HOME", windows = "USERPROFILE" (src/bun_core/env_var.rs:146), so it is populated on Windows too. The practical impact is small because XDG_CONFIG_HOME is rarely set on Windows, but when it is, resolution changes there the same way it does on Linux. Thanks to @coderabbitai for the nudge that surfaced this.

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented Jul 30, 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 Plus

Run ID: 477a8744-1cc8-4b43-84d1-1e876dab2b92

📥 Commits

Reviewing files that changed from the base of the PR and between 04148c8 and ea40a60.

📒 Files selected for processing (2)
  • src/bunfig/arguments.rs
  • test/cli/install/npmrc.test.ts

Walkthrough

get_home_config_path now selects an existing $XDG_CONFIG_HOME/.bunfig.toml and otherwise falls back to $HOME/.bunfig.toml. Integration tests cover XDG precedence, HOME fallback, and empty or unset XDG configuration.

Changes

User config resolution

Layer / File(s) Summary
Shared user config resolver
src/bunfig/arguments.rs
get_home_config_path ignores empty environment values, checks the XDG bunfig file, and falls back to the HOME bunfig file when the XDG file is absent.
npmrc loading and precedence
test/cli/install/npmrc.test.ts
Tests isolate HOME, USERPROFILE, XDG_CONFIG_HOME, and cache settings, then verify the selected cache directory for XDG precedence and HOME fallback cases.

Suggested reviewers: robobun

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #23128 by preferring XDG configuration when present and falling back to HOME configuration when absent.
Out of Scope Changes check ✅ Passed The source and test changes stay within the linked issue scope for shared npmrc and bunfig configuration resolution.
Title check ✅ Passed The title clearly summarizes the main change: falling back to the HOME bunfig when the XDG bunfig is absent.
Description check ✅ Passed The description explains the problem, fix, test coverage, verification results, limitations, and related issue context.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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/bunfig/arguments.rs`:
- Around line 54-55: Add tests covering get_home_config_path and its resolver
for .bunfig.toml: verify the home-directory file is used when the XDG
configuration file is absent, and verify the XDG file takes precedence when both
exist. Include sibling configuration entry points required by the existing test
conventions and assert the resolved paths or contents observably.

In `@test/cli/install/npmrc.test.ts`:
- Line 208: Replace both Bun.$ mkdir invocations in
test/cli/install/npmrc.test.ts at lines 208 and 241 with the filesystem API's
recursive mkdir operation, preserving the existing homeDir and xdgDir directory
setup behavior at each site.
- Line 220: Update both child environment definitions in
test/cli/install/npmrc.test.ts at lines 220-220 and 254-254 to set USERPROFILE
to homeDir alongside HOME, ensuring the fallback and precedence tests use the
fixture profile on Windows.
🪄 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 Plus

Run ID: 1b0bab32-48de-4ed0-bab6-a786893006b5

📥 Commits

Reviewing files that changed from the base of the PR and between bbe3f6a and 85e9194.

📒 Files selected for processing (3)
  • src/bunfig/arguments.rs
  • src/install/PackageManager.rs
  • test/cli/install/npmrc.test.ts

Comment thread src/bunfig/arguments.rs Outdated
Comment thread test/cli/install/npmrc.test.ts Outdated
Comment thread test/cli/install/npmrc.test.ts Outdated
@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Triage note: this is the PR we are keeping for #23128. #36289 was a robobun PR with the same .npmrc fallback (without the .bunfig.toml half); I verified your change on current main and closed that one in favor of this.

What I checked, with your two source hunks applied on top of main (f426a8e):

One thing the branch needs: stderrForInstall was removed from the test harness in #37000 (the harness now suppresses the slow-filesystem warning itself), so the test file no longer merges. The rebase is two lines: drop stderrForInstall from the harness import and use proc.stderr.text() directly in pmCacheWith. That is all I had to change to run it.

Two optional ideas from the closed PRs, take them or leave them:

  • env_var::XDG_CONFIG_HOME.get_not_empty() / HOME.get_not_empty() treat an exported-but-empty variable as unset (with .get(), XDG_CONFIG_HOME="" makes the probe resolve against the cwd). Not a regression from main, which had the same behavior.
  • install: fall back to $HOME/.npmrc when $XDG_CONFIG_HOME is set #36289 kept its repro as a bun publish --dry-run test; if you want an end-to-end auth-token case in addition to the bun pm cache ones, that branch (farm/05a9a319/npmrc-xdg-home-fallback, test/regression/issue/24124.test.ts) has it ready to copy.

#34987 (loading the global bunfig for runtime commands) overlaps this PR on the fallback helper but fixes a different problem and stays open; whichever lands second rebases over the other.

Properrr and others added 2 commits August 13, 2026 04:40
`get_home_config_path` returned `$XDG_CONFIG_HOME/.bunfig.toml` whenever
the variable was set, so exporting `XDG_CONFIG_HOME` (which many Linux
setups do by default) silently stopped `$HOME/.bunfig.toml` from being
read at all.

Probe `$XDG_CONFIG_HOME/.bunfig.toml` and use it only when the file is
actually there, otherwise fall back to `$HOME/.bunfig.toml`. Only one
user-level file is ever loaded, as before.

Rebased onto main after the same fix for the user-level `.npmrc` landed
in PackageManager::init (oven-sh#36289); this commit now only carries the
`.bunfig.toml` half.

Fixes oven-sh#23128
… lookup

Read both variables with get_not_empty() and fold the lookup back into
get_home_config_path, which is its only caller now that the .npmrc side
lives in PackageManager::init.

Cover the four lookup cases through `bun pm cache`, next to the matching
user .npmrc cases: the XDG file wins when present, XDG set without a file
falls back to $HOME, XDG unset, and XDG set to an empty string.
@robobun
robobun force-pushed the claude/xdg-npmrc-home-fallback branch from 90c6522 to ea40a60 Compare August 13, 2026 05:17
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@robobun robobun changed the title install: fall back to $HOME/.npmrc when $XDG_CONFIG_HOME has none bunfig: fall back to $HOME/.bunfig.toml when $XDG_CONFIG_HOME has none Aug 13, 2026
@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

@Properrr, a maintainer asked for this PR to be rebased onto main now that #36289 has landed (correction to my earlier note here: #36289 was merged, not closed, so the .npmrc half of #23128 is already on main). I pushed the rebase to your branch since the PR allows maintainer edits. What changed:

  • The PackageManager.rs hunk is gone; install: fall back to $HOME/.npmrc when $XDG_CONFIG_HOME is set #36289 changed that code and the user-level .npmrc already falls back to $HOME on main. The shared user_config_path helper therefore had a single caller left, so the check now lives directly in get_home_config_path, written the same way as the .npmrc lookup in install: fall back to $HOME/.npmrc when $XDG_CONFIG_HOME is set #36289 (exists_z probe on the XDG candidate, both variables read with get_not_empty() so an empty XDG_CONFIG_HOME counts as unset). Your commit is kept as the first commit on the branch; the second one is the get_not_empty() change plus the tests.
  • The .npmrc tests were dropped because main already has describe("user .npmrc lookup") with the same four cases. The .bunfig.toml tests now sit right after that block as describe("global .bunfig.toml lookup") with the matching four cases (XDG file present wins, XDG set without a file falls back to $HOME, XDG unset, XDG empty), still observed through bun pm cache as in your version.
  • Title and description updated to match; your original description is kept in a collapsed section at the bottom.

Verification on the rebased branch: the two fallback cases fail on the released build and all four pass with the fix (bun bd test test/cli/install/npmrc.test.ts: 35 pass). #34987 is being closed in favour of this PR. The Buildkite build for this fork PR still needs to be unblocked by a maintainer.

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Thanks @Properrr. This change has been carried over to #38313, which is the same diff on an in-repo branch so it can run through CI. Your commit is kept as the first commit there, so the authorship stays with you; the second commit adds the empty-variable handling and the tests.

Closing this one in favor of #38313. #23128 stays open until that lands.

@robobun robobun closed this Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XDG_CONFIG_HOME is breaking bun install

2 participants