Upgrading to Bash 5 on macOS Without Tripping Over Locales

Tatsuhiko Arai (新井 竜彦)

Tatsuhiko Arai (新井 竜彦)

· 7 min read
Just an image

Running kiro-cli doctor produced this nudge:

● Using Bash 3.2.57 may cause issues, it is recommended to either update to bash >=5 or switch to zsh.
  - Install Bash 5 with Brew: brew install bash && bash
  - Change shell default to ZSH: chsh -s /bin/zsh && zsh

Fair enough. macOS still ships Bash 3.2.57 as /bin/bash. That version is from 2007, kept frozen in time because Bash 4 switched to GPLv3 and Apple decided not to ship GPLv3 software. The official path forward, from Apple's point of view, is zsh — the default login shell since Catalina.

brew install bash had already been done, and bash --version in a fresh shell happily reported:

GNU bash, version 5.3.9(1)-release (aarch64-apple-darwin25.1.0)

…and yet kiro-cli doctor was still complaining. Because installing Bash and making it the login shell are two different things.


Step 1 — Tell macOS the new Bash is a legitimate shell

chsh (change shell) only accepts shells that appear in /etc/shells. Homebrew's Bash isn't there by default, so it has to be added.

On Apple Silicon, Homebrew lives under /opt/homebrew:

# Confirm the path first
which -a bash
# /opt/homebrew/bin/bash
# /bin/bash

# Append to /etc/shells (needs sudo)
echo /opt/homebrew/bin/bash | sudo tee -a /etc/shells

Why the gatekeeping? /etc/shells is the OS's allowlist of "shells a regular user is allowed to choose as their login shell." It's a small security measure — without it, a user could chsh -s /usr/bin/curl themselves into a useless account, and FTP/su/PAM tools that consult /etc/shells would happily go along with it.

Heads-up for Intel Macs: the Homebrew prefix is /usr/local, so the path is /usr/local/bin/bash. Substitute accordingly.

And: don't delete /bin/bash. Some system scripts depend on its exact behavior, and SIP-protected paths won't let you anyway.

Step 2 — Actually switch the default shell

chsh -s /opt/homebrew/bin/bash

Open a new terminal window — echo $BASH_VERSION should now read 5.3.x, and kiro-cli doctor should stop nagging.


The next paper cut: a setlocale warning on every login

Victory was short-lived. Every new shell now opened with:

Last login: Tue May  5 08:35:51 on ttys002
bash: warning: setlocale: LC_COLLATE: cannot change locale (): No such file or directory

locale confirmed the situation:

LANG=""
LC_COLLATE="C"
LC_CTYPE="UTF-8"
LC_MESSAGES="C"
...
LC_ALL=

Note the weirdness: LC_CTYPE is the string "UTF-8" — which is an encoding, not a locale. A real locale name looks like en_US.UTF-8 (language_territory.encoding). Bash's setlocale(3) call gets handed "UTF-8" as if it were a full locale name, fails, and prints the warning.

The culprit is Terminal.app's "Set locale environment variables on startup" option, which has historically passed LC_CTYPE=UTF-8 (without a matching LANG) to child shells. macOS-shipped Bash 3.2 silently tolerated it; Bash 5 is stricter and complains.

Step 3 — Set the locale yourself

English UTF-8 is a safe pick (Japanese-system folks can use ja_JP.UTF-8 — both produce identical sort/collation behavior, only the language of error messages differs):

# ~/.bash_profile
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

A quick sanity check on what the system actually has installed:

locale -a | grep -i utf

A subtle point worth knowing: LC_ALL is the nuclear option — it overrides every other LC_* variable. That's exactly what you want for a single-user dev machine, but if you ever want, say, English error messages with Japanese date formatting, set LANG=en_US.UTF-8 plus LC_TIME=ja_JP.UTF-8 individually, and don't set LC_ALL.

Step 4 — Make sure it actually loads in new shells

This is the easy place to trip. After editing .bash_profile, sourcing it manually works fine, but new terminal windows can still open with empty locales. The reason: bash reads different startup files depending on whether it's launched as a login shell or a plain interactive shell.

  • Login shell (-bash, what $0 shows): reads the first of ~/.bash_profile, ~/.bash_login, ~/.profile.
  • Non-login interactive shell (bash): reads ~/.bashrc.

Different terminal emulators do different things. Apple's Terminal.app launches a login shell. iTerm2 does too by default. tmux panes, VS Code's integrated terminal, and various IDE shells often don't. The bullet-proof pattern is to put the actual config in ~/.bashrc, and have ~/.bash_profile source it:

# ~/.bash_profile
[ -f ~/.bashrc ] && . ~/.bashrc
# ~/.bashrc
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8

Both code paths now converge on the same exports.

Step 5 (optional) — Cut the warning at its source

Even with the exports above, the warning fires before .bash_profile runs — Bash sets up its locale during initialization, then reads your dotfiles. To prevent the bad value from reaching Bash in the first place:

Terminal.app → Settings → Profiles → Advanced → "Set locale environment variables on startup" → uncheck.

Combined with the exports in .bashrc, this gives a clean startup with no warning at all.


The short version

  1. brew install bash — gives you Bash 5.x at /opt/homebrew/bin/bash.
  2. echo /opt/homebrew/bin/bash | sudo tee -a /etc/shells — register it as a legitimate login shell.
  3. chsh -s /opt/homebrew/bin/bash — make it the default.
  4. Put export LANG=en_US.UTF-8 and export LC_ALL=en_US.UTF-8 in ~/.bashrc.
  5. Make sure ~/.bash_profile sources ~/.bashrc.
  6. Optionally, uncheck "Set locale environment variables on startup" in Terminal.app to silence the startup warning entirely.

Three things worth internalizing, all of them obvious in hindsight:

  • Installing a shell is not the same as using a shell. The /etc/shells + chsh dance is what flips the switch.
  • LC_CTYPE=UTF-8 is not a valid locale; en_US.UTF-8 is. An encoding alone is not enough.
  • Login shells and interactive shells read different files. When in doubt, source .bashrc from .bash_profile and stop worrying about it.
Tatsuhiko Arai (新井 竜彦)

About Tatsuhiko Arai (新井 竜彦)

Embedded software engineer (Qt, C/C++, Python). Medical imaging (DICOM) contractor. AWS All Certifications Engineer – Japan (2024–2025).

Copyright © 2026 Tatsuhiko Arai. All rights reserved.
Made by Web3Templates· Github