01Where it started
A half-remembered question: a package manager, but is it also installed in /bin? The first guess was pnpm. It was Bun. And Bun isn't in /bin. It lives in a hidden folder in your home, ~/.bun/bin/bun, and the terminal finds it anyway. Each answer raised the next question: what finds it, where the other tools live, why the folders have the names they do, and why Node is in a different place from Bun.
02Typing a name
The window you type into is the Terminal app. What reads your typing is a , an ordinary program; on a Mac it is zsh, the file /bin/zsh. When you type bun install, the shell splits off the first word and has to find a file called bun.
It doesn't search the whole disk. It reads , an holding a list of folders separated by colons, and looks in each folder in turn. The first file with that name runs. If none has it, you get command not found. The name is literal: it is a list of paths. The capitals are a Unix habit for environment variables, like HOME and USER, to tell them apart from a script's own variables.
- ~/.bun/bin
- ~/Library/pnpm
- ~/.local/bin
- /opt/homebrew/bin
- /usr/local/bin
- /usr/bin
- /bin
Looking…
First match wins. That matters more than it sounds. On this Mac, node is two different files. One is /usr/local/bin/node, Node 20, put there by the installer from nodejs.org in 2023. The other is ~/.local/bin/node, Node 22, a link to the copy an agent tool brought with it. ~/.local/bin comes earlier in the list, so typing node runs 22 and the other is never reached. which -a node lists every match in order.
A command is a file. PATH is the order the shell looks in, and the first file it finds is the one that runs.
Apps you click on aren't found this way. Finder and Launchpad open Chrome or Figma from /Applications, and PATH plays no part. PATH is only for names typed into a shell. An app can join in by putting a file on PATH: VS Code's "Install 'code' command in PATH" puts /usr/local/bin/code, a into the app, so code . works. Until you run it, the answer is zsh: command not found: code.
03Where PATH comes from
PATH isn't kept in one file. Every running program holds its own copy of its environment, inherited from whatever started it, and each shell builds its PATH as it starts, in steps.
- The system's list comes first.
/etc/zprofileruns Apple'spath_helper, which reads/etc/paths(/usr/local/bin,/usr/bin,/bin,/usr/sbin,/sbin) and adds whatever installers dropped into/etc/paths.d, such as Homebrew's. - Then your own files:
~/.zprofile, then~/.zshrc. The dot hides the file in Finder; rc stands for run commands. It is a script zsh runs every time a new shell starts. - An installer that wants to be found adds a line like
export PATH="$HOME/.bun/bin:$PATH": the new folder, then everything already there. That puts it at the front.
So the last line written is the first folder searched. Here .zshrc adds ~/.bun/bin, pnpm's folder and ~/.local/bin in front, and that is why they beat /usr/local/bin. Bun's installer wrote its two lines itself and said so when it finished.
Two things trip people up. A shell that's already open read .zshrc when it started, so it won't see an edit until you open a new one or run source ~/.zshrc, which runs the file again in this shell. And each shell reads only its own file: zsh never reads .bashrc, so a line added there does nothing. echo $SHELL says which one you have; Macs have started with zsh since Catalina in 2019. zsh is the program and .zshrc its settings, the same as Bash and .bashrc.
04The folders
The folders that hold programs differ in two ways: who put the files there, and who is allowed to change them.
- /
- bin
- usr
- bin
- local
- bin
- opt
- homebrew
- bin
- Applications
- Users
- you · ~
- .bun
- bin
- Put there by
- Installers you ran
- Changed by
- Anyone with sudo
- Used by
- Every user
- On PATH
- Yes
- For example
- node, code
On a Mac since Big Sur, /bin, /usr/bin and the rest of Apple's system live on a sealed volume that is mounted read only. Not even can write there, and an update replaces it whole. /usr/local and /opt/homebrew sit outside the seal: they belong to the machine, every user can run what is in them, and writing to them takes sudo or, for Homebrew, an owner set once at install. Your home, ~, is /Users/<you>: only yours, and it needs no permission from anyone. On a shared server that difference matters; on a laptop with one person it mostly decides whether you're asked for a password.
And /usr did mean user. The first guess was right. In 1971 Unix ran on a PDP-11 with two small disks. The system filled the first, so it spilled onto the second, which was mounted at /usr and held the users' home folders, and that is how /usr/bin started. Home folders later moved out, to /home on Linux and /Users on a Mac, and the name stayed behind. "Unix System Resources" is a meaning found for the letters afterwards. The folder that is really yours is ~.
/usr/local came later: things added to this one machine on top of the system it shipped with. The split between /bin and /usr/bin was that disk running out, not a ranking of importance. Many Linux systems have since merged them, and /bin is just a link to /usr/bin. On a Mac both are Apple's.
05Two ways to move in
Why is Node in /usr/local/bin and Bun in ~/.bun/bin, rather than ~/bin/.bun? The tool doesn't decide that. The installer does. There are two habits, and Homebrew sits between them.
Into the shared folder. The installer from nodejs.org drops the node file straight into /usr/local/bin, alongside everyone else's, and npm into /usr/local/lib. Every user on the machine gets them. It asks for your password, because those folders aren't yours, and taking it out again means knowing every place it wrote to.
Into a folder of its own. Bun makes ~/.bun, puts its file in a bin folder inside it and adds that folder to PATH. Claude Code does the same in ~/.local: ~/.local/bin/claude is a link to one file in ~/.local/share/claude/versions/, and earlier versions wait beside it. No password, nothing touched outside your home, and deleting one folder removes it. That's why the folder comes first and bin sits inside it: the folder is the tool's own, bin is the one part of it PATH needs to see.
Homebrew does both. It keeps the real files in a cellar, a folder per tool per version, then puts a for each in one shared bin. On this Mac /opt/homebrew/bin/pnpm points to ../Cellar/pnpm/10.30.2/bin/pnpm. Upgrading means a new folder and moving the link. On Apple Silicon Homebrew lives in /opt/homebrew; on Intel Macs it was /usr/local.
- /usr/local/bin/nodeFile
- /usr/local/bin/npm→ ../lib/node_modules/npm/bin/npm-cli.js
- /usr/local/bin/npx→ ../lib/node_modules/npm/bin/npx-cli.js
- /usr/local/lib/node_modules/npm/Folder
- /usr/local/include/node/Folder
- Asks for a password
- Yes. These folders aren't yours
- Gets on PATH
- Nothing to add. /usr/local/bin is on it already
- To take it out
- Five places, by hand, with sudo
Node can arrive either way. Version managers such as nvm put each Node in its own folder in your home, the way Bun does. Once the folder is on PATH, the shell can't tell the difference.
Where a tool lives is the installer's choice. To the shell it only matters that the folder is on PATH.
06Node and Bun side by side
npm comes with Node, so installing Node is enough to have it. pnpm is installed afterwards, usually with npm, and needs Node to run. Bun needs neither.
That isn't because Bun carries Node inside it. Bun is a of its own, built round a different , JavaScriptCore, and written to run the same code and packages Node does. So the two sit side by side and never meet: node app.js runs your code on Node's V8, bun app.js runs it on Bun's engine, and each is found through PATH like anything else. Close enough that most code runs on both, not so close that nothing breaks, which is why a project is tested before it switches. What is actually in Bun? opens both of them up.
07Back to /bin
So: Bun isn't in /bin. It is one file in ~/.bun/bin, which its installer put at the front of PATH in .zshrc, and zsh reads that every time a terminal opens. What is in /bin is the shell itself.
What starts zsh? Not /bin. The is the part of the system that owns the processor, the memory and the disks, and it starts exactly one program, launchd, which starts everything else. When Terminal opens a window, it asks for a shell; the shell reads its startup files and builds PATH; then you type.
And under the kernel: the bootloader loaded it, and the firmware in a chip on the board loaded the bootloader. Below the firmware it is hardware, which is where Why does thinking harder cost more? picks it up, down to the switches.
Every bun install stands on all of it: a shell found in a folder, a list of folders built by a script, a kernel that starts the shell, and a chip that starts the kernel.
Connected
- Note · same thingSuggestedWhat is actually in Bun?
The June chat picked this one up at /usr/local/bin and opened up what is in those folders: node, npm, the one Bun file, and Docker packing the whole tree.
- Note · same thingSuggestedWhat actually runs my code?
The runtimes there are the files here:
nodein /usr/local/bin is V8 and its harness,bunin ~/.bun/bin is JavaScriptCore and another. - Hole · same thingSuggestedWhy does thinking harder cost more?
This note goes down from the shell to the kernel, the firmware and the chip. Thinking starts at the chip and goes down to the switches.
Terms
Checked
What the chat had wrong or only half right, against sources. The note above already says the corrected thing.
- /usr stands for "Unix System Resources", not user.It held the users' home folders. System files spilled onto that second disk when the first filled, in early Unix. The longer name came later.Rob Landley, BusyBox list, 2010
- PATH starts in ~/.zshrc.It starts before that: /etc/zprofile runs path_helper, which reads /etc/paths and /etc/paths.d. The .zshrc only adds folders in front.macOS, /etc/zprofile and man path_helper
- /usr/bin needs sudo to change, and the shell is in /bin because the system needs it to boot.On a modern Mac neither folder can be changed, even with sudo: both are on Apple's sealed, read-only system volume. The /bin and /usr/bin split was about disk space, and the Mac boots without a shell.Apple Platform Security, Signed system volume
- The kernel launches the shell.The kernel starts launchd, which starts everything else. Terminal asks for a login shell when it opens a window.Apple, The boot process
- Node puts itself in /usr/local/bin; that is Node's old-school convention.It is the nodejs.org installer's choice. Homebrew puts Node in /opt/homebrew, and nvm puts each version in its own folder in your home.nvm, README
- Bun has Node.js built in, so you don't need Node.Bun has no Node inside. It is a runtime of its own that runs most Node code. The chat corrected this itself later on.Bun docs, Node.js compatibility
- Claude Code is a fully native binary, not a JavaScript app.It is JavaScript compiled by Bun into one file with Bun's runtime inside. It needs no Node because it carries its own runtime.Bun blog, Bun joins Anthropic