Skip to content

fix: resolve the bun runtime instead of assuming it is on the shell's PATH - #3

Merged
nixfred merged 2 commits into
nixfred:mainfrom
LinuxGamerUK:fix/bun-runtime-resolution
Oct 4, 2026
Merged

nixfred merged 2 commits into
nixfred:mainfrom
LinuxGamerUK:fix/bun-runtime-resolution

Conversation

@LinuxGamerUK

Copy link
Copy Markdown

Problem

The panel spawns its Bun backend with a bare bun command:

statusProcess.command = ["bun", "run", backendPath, "status", "--host", fetchHost, "--compact"]
tailnetProcess.command = ["bun", "run", backendPath, "tailnet", "--compact"]

That has two failure modes, both silent to the user:

  1. Bun not installed — Process fails to start; quickshell logs a WARN to the shell log, but the panel shows no error row, sends no notification, and the startup sweep never completes (the widget sits on "Preparing startup scan" forever).
  2. Bun installed via the official installer (~/.bun/bin/bun) — the binary works in a terminal but is not on quickshell's PATH, because the shell inherits the systemd user session PATH, not the interactive shell's. Same silent failure.

A user who runs command -v bun successfully and still sees a dead widget has no way to connect the two.

Fix

Resolve the runtime once at plugin load with a /bin/sh probe, then spawn the backend with the resolved absolute path:

  • Candidates tried in order: bun on PATH → ~/.bun/bin/bun → /usr/bin/bun → /usr/local/bin/bun (covers pacman, the official installer, and manual installs).
  • The probe itself cannot fail to start (/bin/sh always exists), and is hardened like every other subprocess in the plugin: child runs under timeout -k 2 1, output is capped at the parser, and the result is trusted only on exit 0 with a single absolute path.
  • If no runtime is found, the panel now displays an actionable error naming the fix (Backend runtime not found: install bun (e.g. pacman -S bun), or symlink your bun binary into ~/.local/bin, then restart the shell.), the startup-sweep bookkeeping still completes so cadence timers keep working, and ordinary non-zero backend exits keep quoting their exit code as before.
  • refreshTailnet() / pump() no-op until the runtime resolves, so no spawn is attempted against a missing binary.

Notes for review

  • refreshSelectedHost was already guarded by startupSweepComplete, so the no-op guards do not change normal-path behavior; on machines where bun was already on the session PATH, behavior is identical (probe resolves the same binary, just as an absolute path).
  • Version bumped to 0.10.1 (both manifest.json and the panel constant — the existing test enforces parity).
  • The byte-budget test's splitMarker: "" count moved 4 → 5 for the probe's one capped parser.
  • Full suite green (bun test, 73 pass), omarchy plugin validate clean, qmllint parity with baseline (only the pre-existing expected noise classes, one additional exitStatus warning from the third onExited handler), and live-tested on Omarchy with the resolved path spawning correctly after omarchy-restart-shell.

Closes the silent-failure report; happy to adjust the candidate list or error wording if you'd rather resolve differently.

LinuxGamerUK and others added 2 commits September 27, 2026 14:06
… PATH

The panel spawned the backend with a bare "bun" command. Quickshell
inherits the systemd user session PATH, so a bun installed via the official
installer (~/.bun/bin) or missing entirely made every spawn fail with
"Process failed to start" in the shell log while the panel stayed silent:
no error row, no notification, and a startup sweep that never completed.

Resolve the runtime once at plugin load with a /bin/sh probe (bun on PATH,
~/.bun/bin/bun, /usr/bin/bun, /usr/local/bin/bun), then spawn the backend
with the resolved absolute path. If no runtime can be found, the panel now
shows an actionable error naming the fix instead of failing silently, and
the startup sweep bookkeeping still completes so cadence timers keep work.

Probe hardening matches the house rules for every other subprocess: the
child runs under timeout -k, output is capped at the parser, and the
result is trusted only on exit 0 with a single absolute path.
…g when bun is missing

WHAT: The runtime probe tested every candidate with [ -x ], so the bare name
bun was checked as a file in the current directory and the command -v branch
was unreachable. Bare names now resolve with command -v -- and are accepted only
when the result is an absolute path; absolute candidates still use [ -x ]. The
probe stays one /bin/sh -c string with the single-absolute-path output
contract. When no runtime is found, the startup sweep is also marked started and
complete, so the header no longer reads "Preparing startup scan" beside the
error row.
WHY: A bun on PATH that is not one of the three absolute candidates (a mise
shim, ~/.local/bin/bun, which the error message itself recommends) was never
found, so that fix path could not work.
ENABLES: bun in any PATH directory is found; a missing bun yields one actionable
error and a settled widget.
CONTEXT: Maintainer edit on a contributor PR, crew task oss-pr3-fixes-s1.
Verified headlessly through /bin/sh: real PATH resolves /usr/bin/bun; a symlink
in a temp dir on PATH (absolute fallbacks redirected) resolves that path; no
candidate exits 1; a stray ./bun in the cwd is not matched. bun test 73 pass,
bun run check builds.
TAGS: panel, runtime-probe, bun, command-v, startup-sweep, review-fix

LR-T: fix
LR-D: bare names resolve via command -v, absolute paths via test -x
LR-K: bun, runtime probe, PATH, sh
@nixfred

nixfred commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Reviewed and pushed one fix to this branch (01280ae, maintainer edit). Thanks for tracking this down, @LinuxGamerUK.

Defect found in resolveBackendRuntime(): every candidate was gated by [ -x "$candidate" ], so for the bare name bun the test ran against a file named bun in the current directory. The command -v branch was unreachable, and a bun on PATH that is not one of the three absolute candidates (a mise shim, ~/.local/bin/bun, which the error message itself recommends) was never found.

Fix: bare names resolve with command -v -- "$candidate" and are accepted only if the result starts with /; absolute candidates keep [ -x ]. Still one /bin/sh -c string under timeout -k 2 1, still one absolute path on exit 0. When no runtime is found, storeRuntimeProbe now also marks the startup sweep started and complete, so the header stops reading "Preparing startup scan" next to the error row.

Rest of the review: storeRuntimeProbe trusting only a leading /, the backendReady() gates on refresh()/pump()/refreshTailnet(), and the kill-timer and onExited paths all ending in storeRuntimeProbe are correct. The branch was already up to date with main, so no merge was needed.

Verification (probe string extracted from Panel.qml and run through /bin/sh):

  • real PATH: /usr/bin/bun, exit 0
  • symlink in a temp dir as the only PATH entry, absolute fallbacks redirected away: that symlink path, exit 0 (the command -v branch)
  • nothing found: no output, exit 1
  • an executable ./bun in the cwd with an empty PATH: exit 1 (no longer matched)
  • bun test: 73 pass, 0 fail; bun run check: backend bundles. qmllint is not installed here.

@nixfred
nixfred merged commit 774196d into nixfred:main Oct 4, 2026
2 checks passed
@nixfred

nixfred commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Landed: merge 774196d on main as v0.10.1, CI green on both bun lanes. Verified in a clean Omarchy 4.0.2 VM (Quickshell 0.3.1): with bun present the panel runs the backend and shows its own tailscale-not-found row; with /usr/bin/bun hidden it shows your runtime-not-found row instead of sitting on the startup scan. Only the probe fix (01280ae) was added on top of your work. Thank you, this was a silent failure that would have bitten the official-installer crowd.

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.

2 participants