You installed something by hand and it needs a PATH line. Two files could hold it; they look interchangeable and are not. The rule: the PATH entry belongs wherever the thing it points to lives. Hand-installed on this machine only → ~/.bashrc. Installed automatically on every machine → the dotfiles repo (dotfiles-architecture).

The big picture

DOTFILES REPO · TRACKEDTHIS MACHINE · LOCAL~/dotfiles/shell/bash_aliasestracked · in git · every machineOn a new machinegit clone + bootstrap.sh~/.bash_aliasessymlink (l) into the repo~/.bashrcplain file · dies with a reflashbash · new terminalreads ~/.bashrc at startupNew laptop: should thisline appear by itself?symlink: edits land in gityesnosources itreads at startupreflash-proof
  • ~/.bash_aliases is a pointer into git. Appending to it is a repo edit → needs a branch first.
  • ~/.bashrc is a plain local file. Git has never heard of it; it dies with a reflash.
  • The test: if I set up a brand-new laptop tomorrow, should this line appear on it by itself? Yes → dotfiles. No → .bashrc.
ls -la ~/.bashrc ~/.bash_aliases
lrwxrwxrwx 1 xuebin xuebin   40 Aug 31 13:18 .bash_aliases -> /home/xuebin/dotfiles/shell/bash_aliases
-rw-r--r-- 1 xuebin xuebin 4429 Sep  4 15:03 .bashrc
  • First character: l → symlink · - → ordinary file.
  • Writing to ~/.bash_aliases writes ~/dotfiles/shell/bash_aliases → git sees it → a tracked edit.
  • ~/.bashrc → belongs to this machine; nothing tracks it.
echo 'export PATH=...' >> ~/.bash_aliases   # really a commit-in-waiting in ~/dotfiles

Same-looking edits, completely different kinds

Appending to ~/.bash_aliases trips the “never edit on main” rule. Branch first.

2. Why .bash_aliases exists at all

Not a bash feature: an Ubuntu convention. The stock ~/.bashrc (line 104) sources it if present:

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi
  • Separation: your customisations live outside the file the distro owns.
  • A distro upgrade can replace .bashrc wholesale and destroy nothing of yours. Nothing to merge.

3. Side by side

~/dotfiles/shell/bash_aliases (tracked)~/.bashrc (local)
Under git / needs a branch firstyesno
Survives a reflashyes, via git clone + bootstrap.shno, gone
Appears on a new machineautomaticallynever
History of why you added itgit lognone
Right forthings every machine of yours hasthings only this machine has

The reflash row is the one that bites

A local edit exists on exactly one disk; reinstall the OS and it’s gone with no record. Fine when the software it pointed at is gone too, a silent loss when it wasn’t.

4. The decision test

  • Yes → dotfiles: aliases, prompt settings, editor choice, ~/.local/bin on PATH. They describe you.
  • No → .bashrc: a PATH entry to software unpacked by hand into /usr/local. On a fresh machine that directory won’t exist: a promise the machine can’t keep.
  • A dead PATH entry is harmless (bash skips missing directories), but machine-specific lines turn the repo into a pile of exceptions. Its value is that bootstrap.sh gives a working machine.
  • Promotion path: when a manual install becomes automated (added to ansible/provision.yml, see ansible), the PATH line moves into dotfiles at the same time. Install and PATH entry are one fact.

5. Worked example: Go 1.27.1, 2026-09-09

sudo tar -C /usr/local -xzf go1.27.1.linux-amd64.tar.gz   # from the a2_shenzen_bundle robot bundle
 
cat >> ~/.bashrc <<'EOF'
 
# Go toolchain, installed from tarball to /usr/local/go (go1.27.1).
export PATH="$PATH:/usr/local/go/bin"
EOF
 
source ~/.bashrc
  • Not reproducible: one bundle, one job, this laptop. No other machine has /usr/local/go, and Ansible doesn’t know Go exists → local.
  • Contrast ~/.local/bin → tracked, because pipx puts tools there on every machine and Ansible installs pipx everywhere. Same kind of line, opposite home.
  • If Go becomes standard tooling rather than one bundle’s dependency, it gets an Ansible task and the PATH line moves across. Not before.

6. Append or prepend?

export PATH="$PATH:/usr/local/go/bin"      # append: existing entries win
export PATH="/usr/local/go/bin:$PATH"      # prepend: this one wins
which go                                   # → /usr/local/go/bin/go
  • PATH is searched left to right; first match wins.
  • Append = safer default (Go’s own docs use it): a package-managed go in /usr/bin would win, giving the version your system expects.
  • Prepend only to deliberately override a packaged copy.
  • which shows a surprise? echo $PATH and read left to right.

7. The idempotence guard

case ":$PATH:" in
    *":$HOME/.local/bin:"*) ;;
    *) export PATH="$HOME/.local/bin:$PATH" ;;
esac
  • Used in the tracked file. Without it, every source ~/.bashrc, nested shell and tmux inside tmux appends again → echo $PATH becomes an unreadable wall.
  • The colons around ":$PATH:" and the pattern make the match exact: /usr/local/go/bin can’t match /usr/local/go/bin-old.
  • A plain export PATH="$PATH:..." duplicates on re-source: cosmetic, not harmful. Worth the guard once a file has more than a couple of PATH lines.

8. .bashrc isn’t read by everything

FileRead by
~/.profilelogin shells, and the desktop session at graphical login
~/.bashrcinteractive non-login shells, i.e. every terminal window
~/.bash_aliasesnothing directly; sourced by .bashrc (§2)

PATH set only in .bashrc is invisible to:

  • ssh thishost 'go version' → non-interactive, no .bashrc
  • GUI-launched apps → inherit the session environment, built from .profile at login
  • systemd --user services and cron jobs

Works in the terminal, not in a GUI tool or systemd unit?

This table is why. Put the PATH line in ~/.profile, then log out and back in. (Terminals are fine: Ubuntu’s .profile sources .bashrc for bash anyway.)

Quick reference

ls -la ~/.bashrc ~/.bash_aliases          # l = symlink into dotfiles (tracked), - = local file
export PATH="$PATH:/usr/local/go/bin"     # append (default): existing entries win
export PATH="/usr/local/go/bin:$PATH"     # prepend: deliberately override
which go                                  # which copy won?
echo $PATH                                # read left to right
source ~/.bashrc                          # reload in this terminal