{{ message }}
fix(mac): resolve a Nix-provided node when spawning the codeburn CLI - #1263
Merged
Conversation
On a nix-darwin machine node lives only in /etc/profiles/per-user/$USER/bin, which appears in neither additionalPathEntries nor userNodePaths. The CLI shim begins '#!/usr/bin/env node', so the spawn dies with 'env: node: No such file or directory' and exit status 127. GUI apps normally inherit those directories from 'launchctl config user path', stored in /var/db/com.apple.xpc.launchd/config/user.plist, which a macOS system update deletes. Re-applying it is not durable, so name the Nix locations in userNodePaths instead. That feeds both installedArgv() discovery and augmentedPath() interpreter resolution. Also adds ~/.local/bin, the bin directory for the common 'prefix = ~/.local' npm configuration, which installedArgv() could not otherwise discover. Entries are appended, so existing matches keep their precedence.
3 tasks
iamtoruk
marked this pull request as ready for review
September 6, 2026 07:49
iamtoruk
approved these changes
Sep 6, 2026
iamtoruk
left a comment
Member
There was a problem hiding this comment.
Verified locally: 4/4 CodeburnCLIPathTests pass on the branch, the two new tests fail with exit 127 against main's CodeburnCLI.swift. One nit for a follow-up, not blocking: ~/.local/bin lands before the asdf/mise/nvm entries, so it is inserted rather than appended as the description says. Merging.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
On a Nix-managed machine the menubar app renders no data, because every CLI spawn fails with
env: node: No such file or directoryand exit status 127.CodeburnCLI.makeProcessruns the CLI through/usr/bin/envwithPATHaugmented byadditionalPathEntries(/opt/homebrew/bin,/usr/local/bin) plususerNodePaths(volta,.npm-global, asdf, mise, nvm). On a nix-darwin machinenodeexists in none of those. It lives in the per-user profile at/etc/profiles/per-user/$USER/bin. The published npm launcherdist/cli.jsbegins#!/usr/bin/env node, soenvperforms a PATH lookup that cannot succeed, and the child dies before any application code runs.The failure is latent rather than constant, which is why it presents as a sudden break. GUI applications normally receive those directories from
launchctl config user path, which is stored in/var/db/com.apple.xpc.launchd/config/user.plist. A macOS system update deletes that file. Afterwardslaunchctl getenv PATHreturns empty, the app inherits the bare/usr/bin:/bin:/usr/sbin:/sbin, and the app is permanently unable to spawn its CLI. Re-applying the launchd setting is not a durable fix, because the next system update removes it again; the application has to know where Nix puts things.userNodePathsnow also names the Nix locations, in this order:/etc/profiles/per-user/$USER/bin~/.nix-profile/binnix profile, classic layout~/.local/state/nix/profiles/profile/binnix profile, XDG state layout/run/current-system/sw/bin/nix/var/nix/profiles/default/binThe username is derived from the injected
homeDirectoryrather than read from the process environment, so the function stays pure and remains testable with a fixture home.~/.local/binjoins the same list. That is thebindirectory for the commonprefix = ~/.localnpm configuration, andinstalledArgv()previously could not discover a CLI installed there. Reaching it depended entirely on the cachedcodeburn-cli-path.v1; with that cache absent, discovery fell through to the bare namecodeburn, which then hit the same unresolvable PATH lookup.Because
userNodePathsfeeds bothinstalledArgv()andaugmentedPath(), one addition fixes CLI discovery and interpreter resolution together. Entries are appended, so every currently-matching directory keeps its existing precedence and no machine without Nix changes behavior.Change evidence
userNodePathsnodewithout depending on launchd state that macOS updates delete.swift test --filter CodeburnCLIPathTests: 4 tests in 1 suite passed, 0 failed, 0.163 s. Test title: "Spotlight-minimal PATH can launch a CLI whose only node is a Nix profile". The fixture puts the onlynodein$HOME/.nix-profile/bin, points a#!/bin/sh/exec node "$@"wrapper at it to mirror the real shim's PATH-based interpreter lookup, and runs that wrapper through/usr/bin/envon a PATH filtered to/usr/bin,/bin, and the entries productionaugmentedPathderived under the fixture home.swift test --filter CodeburnCLIPathTests: 4 passed, 0 failed, 0.163 s. Test title: "nix-darwin per-user profile is derived from the home directory". It asserts thataugmentedPathforhomeDirectory: "/Users/test"contains/etc/profiles/per-user/test/binand/run/current-system/sw/bin.mac/Sources/CodeBurnMenubar/Security/CodeburnCLI.swiftalone viagit stash pushon that single path, kept both tests, and re-ran the same command: rc 1, "Test run with 4 tests in 1 suite failed after 0.115 seconds with 4 issues". The failure is the reported symptom:Expectation failed: (process.terminationStatus → 127) == 0, together withExpectation failed: (output → "") == ("nix-node-ok\n" ...). Restored withgit stash pop.~/.local/bininuserNodePathsinstalledArgv()could not discover a CLI under the commonprefix = ~/.localnpm configuration.npm config get prefixreturns/Users/<user>/.local, andnpm ls -g --depth=0reportscodeburn@0.9.24under/Users/<user>/.local/lib, whileadditionalPathEntries + userNodePathscontained no~/.local/bin. No dedicated test: this half is filesystem probing insideinstalledArgv(), and the Nix test above already covers theaugmentedPathhalf.CodeBurnMenubar.app/Contents/MacOS/CodeBurnMenubarwithPATH=/usr/bin:/bin:/usr/sbin:/sbinand no launchd PATH assistance.pgrep -P <app pid>reported34818 34811 node /Users/<user>/.local/bin/codeburn serve --stdio, ppid matching the app, and the app stayed alive. The pre-fix binary under the same bare PATH stayed alive but spawned no child within 18 s, measured the same way. The exit-127 status itself was captured at the spawn level rather than from that process:/usr/bin/env -- ~/.local/bin/codeburn --versionunder a GUI-representative PATH of/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbinreturned rc 127 withenv: node: No such file or directory, and rc 0 with0.9.24once the Nix directories were present.mac/, so the existing suite must stay green.npm test: 272 test files passed, 2 skipped; 3738 tests passed, 5 skipped, 0 failed; duration 34.30 s. Rannpm run build: rc 0.Environment
Observed and fixed on macOS 27.0 with nix-darwin, Apple Silicon.
which node/etc/profiles/per-user/<user>/bin/node, realpath/nix/store/...-nodejs-slim-24.14.1/bin/nodenodeunder/opt/homebrew/binor/usr/local/binlaunchctl getenv PATH/var/db/com.apple.xpc.launchd/config/user.plist~/.local/bin/codeburn, first line#!/usr/bin/env nodeNotes
The change is confined to one function. It adds no dependency, alters no public interface, and leaves
isSafe,baseArgv,installedArgv's ordering semantics,persistedCLIPath, and theCODEBURN_BINoverride untouched. Every new entry satisfies the existingsafeArgPattern, which excludes shell metacharacters.A stronger follow-up is deliberately out of scope: persisting the absolute interpreter path beside
codeburn-cli-path.v1and spawning[node, cli.js]would remove the PATH dependency altogether rather than enumerating more locations. This pull request keeps the existing/usr/bin/envdesign and only teaches it the Nix layout.The machine-level remedies used to restore the affected workstation, a login LaunchAgent re-applying the GUI PATH and a post-update verification script, are local configuration and are not part of this change.
Companion pull request #1262 repairs
mac/Scripts/build-local.sh, which on a current toolchain exits before compiling and omits the SwiftPM resource bundle from the assembled app. There is no file overlap, but that fix is what makes the local build and the end-to-end verification above possible, so #1262 should land first.Testing
npm testpassesnpm run buildsucceedsReal-data check: the rebuilt bundle was installed to
~/Applications/CodeBurnMenubar.appand run against the live local corpus.codeburn statusreportedToday $9.77 119 calls / Month $149.97 917 calls, and the menu bar rendered a flame icon with a dollar figure, showing no dash, placeholder, or error state. Itscodeburn serve --stdiochild was confirmed alive by ppid.Files (2)