Skip to content

bunfig: load global ~/.bunfig.toml for runtime commands; fall back past $XDG_CONFIG_HOME - #34987

Closed
robobun wants to merge 10 commits into
mainfrom
claude/farm/55a9ccd1/global-bunfig-runtime
Closed

bunfig: load global ~/.bunfig.toml for runtime commands; fall back past $XDG_CONFIG_HOME#34987
robobun wants to merge 10 commits into
mainfrom
claude/farm/55a9ccd1/global-bunfig-runtime

Conversation

@robobun

@robobun robobun commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Related: #23128 (the $XDG_CONFIG_HOME -> $HOME fallback itself is being landed via #36486; this PR is kept for loading the global bunfig in runtime commands, and its copy of the fallback can be dropped on rebase).

Repro

H=$(mktemp -d); export HOME=$H; mkdir -p $H/.config; cd $H
printf 'globalThis.G="RAN";\n' > $H/g.ts
printf 'preload=["%s/g.ts"]\n[test]\npreload=["%s/g.ts"]\n' $H $H > $H/.bunfig.toml
printf 'console.log(globalThis.G ?? "preload NOT run")\n' > f.ts
unset XDG_CONFIG_HOME; bun f.ts; bun -e 'console.log(globalThis.G ?? "preload NOT run")'
# -> preload NOT run  x2

And with XDG_CONFIG_HOME set to an empty dir (standard on most desktops), [install] saveTextLockfile = false in $HOME/.bunfig.toml is ignored by bun install.

Cause

Two gaps between the documented behavior and the loader in src/bunfig/arguments.rs:

  1. Tag::read_global_config() only matched install-family commands plus bunx. AutoCommand, RunCommand, RunAsNodeCommand, TestCommand and BuildCommand never loaded the global file at all, so its preload, define, and [test] fields had no effect under bun <file> / bun -e / bun run / bun test.
  2. get_home_config_path() returned the $XDG_CONFIG_HOME/.bunfig.toml path whenever the env var was set, without checking that the file exists. On a desktop where XDG_CONFIG_HOME is set (most of them), $HOME/.bunfig.toml was silently dead even for install commands.

Fix

  • read_global_config() now matches LOADS_CONFIG[self]: every command that loads a local bunfig.toml loads the global one first, so the documented shallow merge applies uniformly.
  • get_home_config_path() checks bun_sys::exists_z on the XDG candidate and falls through to $HOME/.bunfig.toml when there is no file at the XDG path. Both env vars are read with get_not_empty() so an empty string is treated as unset.
  • The loaded_bunfig flag moves from load_bunfig (set on any successful parse) to load_config_path (set when the local-config load is attempted). Without this, loading the global file would set the flag and short-circuit the run_command.rs / repl_command.rs local-load fallbacks used by bun run <script>.
  • load_config()'s global block now calls load_global_bunfig() directly instead of routing through load_config_path with the home path; same effect, one less redundant path computation.

Tests

test/config/bunfig/global-bunfig-runtime.test.ts covers bun <file>, bun -e, bun run, bun test, the local-overrides-global merge for bun run, the XDG→HOME fallback (via both a runtime command and bun pm cache), and XDG-wins-when-present.

test/cli/install/bun-run-bunfig.test.ts previously had a characterization test asserting the home bunfig is not loaded for bun run; it now asserts it is, matching the docs.

USE_SYSTEM_BUN=1 bun test test/config/bunfig/global-bunfig-runtime.test.ts  -> 1 pass, 7 fail
bun bd test test/config/bunfig/global-bunfig-runtime.test.ts                -> 8 pass
bun bd test test/cli/install/bun-run-bunfig.test.ts                         -> 28 pass
bun bd test test/config/bunfig/                                             -> all pass

The XDG->HOME fallback here also covers the user-level .npmrc lookup in PackageManager; both fallback hunks duplicate #36486 and are not what this PR is kept open for.

Related: #30853 extends the XDG lookup to the conventional $XDG_CONFIG_HOME/bun/bunfig.toml path and will rebase cleanly on top of this (the fallback logic here is a strict subset).


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/install/bun-run-bunfig.test.ts

… $HOME when XDG path has no config

Two long-standing gaps between docs and behavior:

1. read_global_config() only matched install-family commands plus bunx.
   The global ~/.bunfig.toml's top-level fields (preload, define) and
   [test] section were never applied to bun <file>, bun -e, bun run, or
   bun test.

2. get_home_config_path() returned the $XDG_CONFIG_HOME path whenever
   the env var was set, without checking if the file existed. On
   desktops where XDG_CONFIG_HOME is set by default, $HOME/.bunfig.toml
   was silently ignored.

The global config is now loaded for every command that loads a local
bunfig (read_global_config() == LOADS_CONFIG[cmd]), and the XDG path is
only used when the file actually exists there.

The loaded_bunfig flag moves from load_bunfig (set on any successful
parse) to load_config_path (set when the local-config load is attempted)
so the run_command.rs fallbacks still fire for bun run / bun <script>
after the global file has been read.
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

Bun now falls back from missing XDG configuration files to home-directory .bunfig.toml and .npmrc files. Global loading, command selection, and parser behavior were updated, with tests covering runtime commands, cache operations, precedence, local overrides, and isolated environments.

Configuration fallback and loading

Layer / File(s) Summary
Bunfig resolution and loading
src/options_types/command_tag.rs, src/bunfig/arguments.rs, src/bunfig/lib.rs
Global configuration selection follows LOADS_CONFIG; XDG paths are used only when files exist, and loading state and error reporting are centralized.
Command and parser behavior
src/bunfig/bunfig.rs
Node-compatible commands participate in run and npm configuration handling; test option conflicts, JSX parsing, and serve-static configuration assignment were updated.
npmrc fallback resolution
src/install/PackageManager.rs
npmrc loading checks for an existing XDG or home-directory file before including the global path.
Runtime and environment validation
test/config/bunfig/global-bunfig-runtime.test.ts, test/cli/install/bun-run-bunfig.test.ts
Tests cover global and local configuration, XDG precedence and fallback, npmrc fallback, runtime commands, cache operations, and isolated subprocess environments.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes in bunfig parsing for TestCommand, JSX handling, and parse_serve_static are unrelated to #23128's fallback fix. Split or remove the TestCommand, JSX, and parse_serve_static edits unless they are required for this issue.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the #23128 fallback for .bunfig.toml and .npmrc, including XDG precedence and runtime/load-path behavior.
Title check ✅ Passed The title clearly summarizes global bunfig loading for runtime commands and the XDG-to-HOME fallback.
Description check ✅ Passed The description explains the problem, implementation, related issues, and verification results in sufficient detail.

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

@robobun

robobun commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 4:21 PM PT - Jul 21st, 2026

@robobun, your commit eae02b4 has 1 failures in Build #77131 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34987

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

bun-34987 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. XDG_CONFIG_HOME is breaking bun install #23128 - XDG_CONFIG_HOME blocks fallback to $HOME/.bunfig.toml; this PR's fix to get_home_config_path() checks if the XDG path actually exists before returning it

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

Fixes #23128

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. bunfig: look up XDG-conventional global config paths #30853 - Both modify get_home_config_path() in src/bunfig/arguments.rs to fix XDG_CONFIG_HOME fallback logic for global bunfig.toml loading

🤖 Generated with Claude Code

@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/bunfig/arguments.rs:124-129 — When ~/.bunfig.toml has a parse error, bun repl and standalone (--compile) executables now silently skip loading the local ./bunfig.toml with no diagnostic — a regression, since pre-PR these paths ignored the global config and loaded the local one. Meanwhile bun run/bun test/bun -e crash loudly via report_bunfig_load_failure on the same broken file, so this is also inconsistent. Either call report_bunfig_load_failure(ctx.log, err) in the auto_loaded arm here (matching load_config), or fall through to the local load instead of return Ok(()).

    Extended reasoning...

    What changed

    This PR flips RunCommand.read_global_config() from false to true (it now returns LOADS_CONFIG[RunCommand]). That makes the pre-existing global-load block in load_config_path newly reachable for callers passing RunCommand:

    if cmd.read_global_config() {                           // now true for RunCommand
        if let Err(err) = load_global_bunfig(cmd, ctx) {
            if auto_loaded {
                return Ok(());                              // <-- err dropped; local load below never runs
            }
            ...exit(1)...
        }
    }
    ctx.debug.loaded_bunfig = true;                         // <-- never reached on the swallow path
    load_bunfig(cmd, auto_loaded, config_path, ctx)         // <-- local ./bunfig.toml — never reached

    Affected entry points

    Two direct callers hit this on a fresh ctx where has_loaded_global_config == false:

    • bun replrepl_command.rs:50 calls load_config_path(RunCommand, /*auto_loaded*/ true, "bunfig.toml", ctx). LOADS_CONFIG[ReplCommand] is false (ReplCommand isn't in the table), so any earlier load_config(ReplCommand, ..) skips the global block and leaves has_loaded_global_config = false. This is the first place the global config is attempted.
    • Standalone executablesrun_command.rs:1150 (the bun build --compile runtime path via write_context_no_parse) has the identical shape.

    The other RunCommand callers (run_command.rs:931, :2397) are reached only after load_config has already run with a LOADS_CONFIG=true tag, which either sets has_loaded_global_config = true (making load_global_bunfig a no-op here) or crashes via report_bunfig_load_failure — so they don't hit this.

    Step-by-step proof

    1. User has ~/.bunfig.toml containing preload = [ (unterminated array — TOML parse error).
    2. User runs bun repl in a project with a valid ./bunfig.toml setting preload = ["./setup.ts"].
    3. repl_command.rs:49 sees ctx.debug.loaded_bunfig == false → calls load_config_path(RunCommand, true, "bunfig.toml", ctx).
    4. RunCommand.read_global_config()LOADS_CONFIG[RunCommand]true (post-PR; was false pre-PR).
    5. load_global_bunfig sees has_loaded_global_config == false, sets it true, calls load_bunfig(.., true, "~/.bunfig.toml", ..). The file exists so to_source succeeds; Bunfig::parse returns Err on the TOML syntax error, which propagates out of load_global_bunfig.
    6. Back in load_config_path: Err(err) + auto_loaded == truereturn Ok(()). The error is dropped; ctx.debug.loaded_bunfig is never set; load_bunfig(.., "bunfig.toml", ..) for the local config never runs.
    7. Result: repl starts with neither config applied and no diagnostic printed.

    Pre-PR step 4 evaluated false, control fell straight to line 130, and ./bunfig.toml loaded — so this is a regression in the local-config load, not just a change in how the (previously-ignored) global error is handled.

    Why it matters / inconsistency

    load_config (used by bun <file>, bun -e, bun run, bun test) handles the same failure by calling report_bunfig_load_failure(ctx.log, err) and crashing. So the same broken ~/.bunfig.toml produces a loud error under most commands but a silent no-config under bun repl / compiled binaries. The standalone case is the worse one: an end-user of a compiled app may have an unrelated broken ~/.bunfig.toml from some other tool and will simply see the app run without its bundled-in bunfig.toml settings, with nothing on stderr to explain why. This is the "never swallow a failure or signal success on one" pattern from REVIEW.md §Error handling, and "fix the whole class" — load_config reports the failure, its sibling load_config_path direct callers do not.

    Fix

    Either behavior is defensible, but the current silent-swallow-and-skip-local is neither:

    • Match load_config: replace return Ok(()) with report_bunfig_load_failure(ctx.log, err) so a broken global config fails loudly everywhere; or
    • Fall through: drop the return Ok(()) and let control reach the local load_bunfig call (has_loaded_global_config is already set so it won't retry the global). This preserves the pre-PR local-load behavior while additionally trying the global first.

    Given the PR's goal is uniform behavior across commands, matching load_config is probably the right call.

Comment thread test/cli/install/bun-run-bunfig.test.ts
Comment thread test/config/bunfig/global-bunfig-runtime.test.ts Outdated
…solate bun-run-bunfig.test.ts from host ~/.bunfig.toml; drop strict stderr assertions

Also extend the XDG->HOME fallback to the user-level .npmrc lookup in
PackageManager (fixes #23128).
@robobun

robobun commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed in 8a990ae:

  • load_config_path now reports a malformed global bunfig via report_bunfig_load_failure instead of silently returning Ok(()), so bun repl and standalone executables fail the same way bun run/bun test do on a broken ~/.bunfig.toml (and the local load is no longer silently skipped).
  • test/cli/install/bun-run-bunfig.test.ts spawns now use an isolatedEnv that clears HOME/USERPROFILE/XDG_CONFIG_HOME.
  • dropped the strict expect(stderr).toBe("") assertions in the new test file.
  • extended the XDG->HOME fallback to the user-level .npmrc lookup in PackageManager as well, so this now fully covers XDG_CONFIG_HOME is breaking bun install #23128.

On the #30853 duplicate flag: that PR adds the XDG-conventional $XDG_CONFIG_HOME/bun/bunfig.toml lookup (a new path). This PR is the narrower "existing paths don't work as documented" fix: runtime commands never read the global file at all, and the XDG path never fell back to $HOME. #30853 doesn't touch read_global_config() or the .npmrc lookup, so they're complementary.

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

🤖 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 `@test/config/bunfig/global-bunfig-runtime.test.ts`:
- Around line 138-151: Extend the global npmrc coverage near the existing “XDG
set but no config” test with a case where both HOME/.npmrc and XDG/.npmrc define
distinct cache paths. Run bun pm cache using baseEnv and assert it selects the
XDG cache value rather than the HOME value, covering XDG precedence in the
relevant alternate mode.
🪄 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: d1bedfe9-bd1f-4bdb-b932-ef66233cd296

📥 Commits

Reviewing files that changed from the base of the PR and between 98fb0ac and d60363b.

📒 Files selected for processing (5)
  • src/bunfig/arguments.rs
  • src/install/PackageManager.rs
  • src/options_types/command_tag.rs
  • test/cli/install/bun-run-bunfig.test.ts
  • test/config/bunfig/global-bunfig-runtime.test.ts

Comment thread test/config/bunfig/global-bunfig-runtime.test.ts
Comment thread src/options_types/command_tag.rs
Comment thread src/install/PackageManager.rs Outdated
robobun and others added 2 commits July 21, 2026 19:18
…OME config-path helper

- bunfig.rs gates for preload/serve/telemetry/smol and [run]/[install] now
  match RunAsNodeCommand so the global bunfig parsed during
  load_config(RunAsNodeCommand, ...) applies those keys. The two adjacent
  RunCommand/AutoCommand blocks are merged.
- New home_config_path(buf, name) in bun_bunfig::arguments is shared by the
  .bunfig.toml and .npmrc lookups; PackageManager.rs drops its inline copy.
- Added a node-shim (argv0=node) test to global-bunfig-runtime.test.ts.
Comment thread src/options_types/command_tag.rs
Comment thread test/config/bunfig/global-bunfig-runtime.test.ts Outdated
Comment thread src/options_types/command_tag.rs
Comment thread src/options_types/command_tag.rs
…two dual-parse merges

- load_global_bunfig returns early when StandaloneModuleGraph::get() is
  Some: a compiled --compile binary must not apply the end user's
  ~/.bunfig.toml preload/define to the shipped app (nor crash on a
  malformed one). The autoloadBunfig compile flag continues to gate the
  cwd-local bunfig.toml only.
- [test].retry/rerunEach mutual-exclusion now checks the sibling key in
  the current file and zeroes the counterpart on assignment, so a local
  file can cleanly switch a global retry to rerunEach (or vice versa).
- [serve.static].bunfig_path is written alongside serve_plugins so a
  relative plugin path from a global [serve.static] resolves against the
  file that declared it.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bunfig/arguments.rs (1)

26-37: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require a usable XDG config file before preferring it.

An XDG .bunfig.toml or .npmrc directory satisfies exists_z, suppressing a valid HOME-file fallback. The bunfig directory-read error is then silently ignored as auto-loaded. Select XDG only for a regular readable file; otherwise fall back to HOME, and add this directory case to the regression matrix.

🤖 Prompt for 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.

In `@src/bunfig/arguments.rs` around lines 26 - 37, Update the XDG config
selection logic around env_var::XDG_CONFIG_HOME and the
resolve_path::join_abs_string_buf_z call to prefer the XDG candidate only when
it is a regular readable configuration file, not merely an existing directory.
If the candidate is unusable, continue to the HOME-file fallback, and add a
regression case covering an XDG directory at the config path.
🤖 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.

Outside diff comments:
In `@src/bunfig/arguments.rs`:
- Around line 26-37: Update the XDG config selection logic around
env_var::XDG_CONFIG_HOME and the resolve_path::join_abs_string_buf_z call to
prefer the XDG candidate only when it is a regular readable configuration file,
not merely an existing directory. If the candidate is unusable, continue to the
HOME-file fallback, and add a regression case covering an XDG directory at the
config path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c77c8a8-71fa-481e-b90e-c2116cd0ba7b

📥 Commits

Reviewing files that changed from the base of the PR and between d60363b and eae02b4.

📒 Files selected for processing (5)
  • src/bunfig/arguments.rs
  • src/bunfig/bunfig.rs
  • src/bunfig/lib.rs
  • src/install/PackageManager.rs
  • test/config/bunfig/global-bunfig-runtime.test.ts

@robobun

robobun commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

On the two latest coderabbit items:

  • Out-of-scope warning (jsx / [test] retry / parse_serve_static): these are not stray edits; they were requested in earlier review rounds on this PR to keep the global->local shallow merge correct now that runtime commands parse both files. They stay.
  • $XDG_CONFIG_HOME/.bunfig.toml as a directory: not applying. That case behaves exactly as it did before this PR (the XDG candidate was returned unconditionally, the subsequent read failed, and the auto-load swallowed it). Falling through to $HOME only when the XDG path is a regular readable file would require a stat instead of access; given how pathological a directory named .bunfig.toml is, and that the behavior is unchanged from main, this is beyond the scope here.

@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/bunfig/bunfig.rs:716-722 — Two more same-class one-way-merge gaps beyond retry/rerunEach and serve.static.bunfig_path above: (1) [run].noOrphans (bunfig.rs:~820) only calls ParentDeathWatchdog::enable() when true — there is no disable(), so global [run] noOrphans=true cannot be overridden by local noOrphans=false now that RunCommand/AutoCommand/RunAsNodeCommand dual-parse. (2) load_env_config() (bunfig.rs:~280, called unconditionally at :379) only ever writes disable_default_env_files = true — the env=true / env.file=true branches are no-ops, so global env=false cannot be re-enabled by local env=true. For (2) an else { … = false; } suffices; for (1) enable() does irreversible work during parse (prctl, setenv, Job Object on Windows), so store into a ctx field and arm once after config loading completes.

    Extended reasoning...

    What the bugs are

    read_global_config() now returns LOADS_CONFIG[self], so Bunfig::parse runs twice (global ~/.bunfig.toml then local ./bunfig.toml, sharing one ctx) for RunCommand/AutoCommand/RunAsNodeCommand/TestCommand/BuildCommand. Beyond the jsx.runtime case fixed in f8d9ef5 and the retry/rerunEach + serve.static.bunfig_path cases in the earlier comment, two more handlers in bunfig.rs only ever write one direction and so cannot be overridden by the local file:

    (1) [run].noOrphans — inside the is_npm_related() || RunCommand || AutoCommand || RunAsNodeCommand || TestCommand gate this hunk touches:

    if let Some(no_orphans) = run_expr.get(b"noOrphans") {
        if let Some(value) = no_orphans.as_bool() {
            if value {
                bun_io::ParentDeathWatchdog::enable();
            }
        }
        // no else branch, and no ParentDeathWatchdog::disable() exists
    }

    enable() (src/io/ParentDeathWatchdog.rs) is one-way: it does ENABLED.swap(true), calls bun_spawn_sys::pdeathsig::set_default(true) (arms PR_SET_PDEATHSIG=SIGKILL for spawned children on Linux), does libc::setenv("BUN_FEATURE_FLAG_NO_ORPHANS", "1"), and on Windows creates+assigns a kill-on-close Job Object. There is no disable() in the module.

    (2) top-level envload_env_config() is called unconditionally at bunfig.rs:379 (no command-tag gate) and its body only ever writes true:

    ExprData::EBoolean(boolean) => {
        if !boolean.value {
            self.ctx.args.disable_default_env_files = true;
        }
        // no else: env=true writes nothing
    }

    Same shape for ENull and the nested env.file boolean arm. disable_default_env_files is consumed by run_env_loader in run_command.rs / transpiler.rs to decide whether .env/.env.local/.env.development are auto-loaded — exactly the AutoCommand/RunCommand/TestCommand paths this PR newly enables global→local dual-parse for.

    Step-by-step proof — (1) noOrphans

    • ~/.bunfig.toml: [run] noOrphans = true
    • ./bunfig.toml: [run] noOrphans = false
    • Command: bun run <script> (RunCommand)
    1. RunCommand.read_global_config() = LOADS_CONFIG[RunCommand] = trueload_global_bunfig(RunCommand, ctx)Bunfig::parse on ~/.bunfig.tomlvalue == trueParentDeathWatchdog::enable() runs immediately (atomic flipped, PR_SET_PDEATHSIG default set, env var exported).
    2. Run::bootload_config_path(RunCommand, …)Bunfig::parse on ./bunfig.toml with the same ctxvalue == false → the if value body is skipped. Nothing un-flips the atomic or clears the env var.
    3. Every subprocess spawned via bun run gets PR_SET_PDEATHSIG=SIGKILL on Linux despite the local override.

    Pre-PR, step 1 did not happen for RunCommand/AutoCommand/RunAsNodeCommand (they were absent from the old matches!), so the observable pre-PR result for this scenario was "watchdog disabled" (global ignored entirely). Post-PR it is "watchdog enabled" — a narrow regression against the documented shallow-merge contract. (is_npm_related() commands did dual-parse pre-PR, so the gap was technically reachable for bun install lifecycle scripts, but [run].noOrphans is primarily a run-family setting.)

    Step-by-step proof — (2) env

    • ~/.bunfig.toml: env = false
    • ./bunfig.toml: env = true
    • Command: bun ./main.ts (AutoCommand)
    1. AutoCommand.read_global_config() = true → global parse → load_env_config: !boolean.valuectx.args.disable_default_env_files = true.
    2. Local bunfig.toml auto-loaded (positional matches DEFAULT_LOADERS) → load_env_config: boolean.value == true → the if !boolean.value body is skipped. Field stays true.
    3. run_env_loader(disable_default_env_files=true).env files are not loaded despite the local override.

    Not a strict regression (pre-PR, global env=false was never applied to runtime commands at all, so .env loaded either way for this scenario), but it violates the local-overrides-global contract this PR is enabling — same class as the jsx.runtime gap already fixed in f8d9ef5.

    Why existing code doesn't prevent it

    Both handlers were written when Bunfig::parse ran at most once for the commands that consume these flags, so "false == default == write nothing" was correct. The dual-parse this PR enables makes the missing false branch observable. Neither is a duplicate of the earlier comment: that one enumerates [test].retry/rerunEach and serve.static.bunfig_path; these are two distinct keys with distinct fixes.

    Suggested fix

    • env: add the else { self.ctx.args.disable_default_env_files = false; } branch to both the top-level EBoolean arm and the nested env.file EBoolean arm, so last-write-wins matches the other keys.
    • noOrphans: because enable() performs irreversible side effects during parse (setenv, prctl default, Windows Job Object), an else-branch alone can't undo it. Store the value into a ctx field on both branches (last-write-wins) and call ParentDeathWatchdog::enable() once after config loading completes, mirroring how other deferred side effects are handled.

    Both are narrow triggers (user must set the key one way globally AND explicitly the other way locally), hence nit — same treatment as the earlier same-class comment.

Comment thread test/cli/install/bun-run-bunfig.test.ts
@robobun

robobun commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

On the two remaining dual-parse one-way-merge items ([run].noOrphans and top-level env): not applying here. As noted on the previous round, a full audit of every ctx write in bunfig.rs for dual-parse safety is out of scope for this fix; each round has surfaced another field and the list is open-ended. Neither is a practical regression:

  • env: global env=false was never applied to runtime commands before this PR, so .env loaded either way; post-PR the global value now applies, which is a strict improvement. The local-override-back-to-true case is the gap.
  • noOrphans: fixing this properly means deferring ParentDeathWatchdog::enable() out of the parser (it does irreversible prctl/setenv/Job-Object work at parse time), which is a structural change.

Both are good candidates for a dedicated "make bunfig.rs dual-parse-safe" follow-up that walks every handler once.

@robobun

robobun commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

CI status on eae02b4 (build 77131): the new test/config/bunfig/global-bunfig-runtime.test.ts and the updated test/cli/install/bun-run-bunfig.test.ts pass on every lane. Remaining red is unrelated to this change:

  • test/js/bun/http/bun-server.test.ts on darwin aarch64: "should not use 100% CPU when websocket is idle" fails its CPU%% threshold (2.5-3.5%% sampled); this is a load-sensitive measurement test unrelated to config loading.
  • test/cli/run/transpiler-cache.test.ts on Windows aarch64 (EBUSY on rmSync of a temp dir)
  • test/js/node/test/sequential/test-gc-http-client-timeout.js on Ubuntu x64 (GC collection stuck at 127/128)
  • test/js/third_party/es-module-lexer/es-module-lexer.test.ts on Windows 2019 x64 (90s timeout)

The bun-install-registry.test.ts peer-hoisting failure from the previous build (77111) did not recur on 77131. This diff is ready for review.

Comment thread src/bunfig/bunfig.rs
@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Triage note: #36486 is the PR being kept for #23128 (the $XDG_CONFIG_HOME -> $HOME fallback for both .npmrc and .bunfig.toml; #36289 was closed as its duplicate). Checked on current main with #36486's change applied: this PR's bun pm cache cases (bunfig fallback, npmrc fallback, XDG npmrc wins) pass there, while the seven cases that depend on runtime commands (bun <file>, bun -e, bun run, bun test, the node shim, and the XDG cases exercised through a runtime command) still fail, since neither main nor #36486 loads the global file for those commands. So this PR stays open for that part; the fallback hunks in arguments.rs / PackageManager.rs are now duplicated with #36486 and can be dropped when this is rebased (it currently conflicts with main in bunfig.rs, lib.rs, and bun-run-bunfig.test.ts). I have changed the Fixes #23128 line in the description to a reference so the issue is tracked by #36486.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as a duplicate. The $XDG_CONFIG_HOME -> $HOME fallback from this PR has landed for .npmrc in #36289 and is being landed for .bunfig.toml via #36486, which has been rebased onto main and now carries exactly that change. Loading the global bunfig for runtime commands (bun run, bun test, bun <file>) was the remaining part of this PR; it is a separate behaviour change, not needed for #23128, and can be proposed on its own if there is still interest.

@robobun robobun closed this Aug 13, 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