Brew install the AWS CLI and keep its credentials straight
brew install awscli finishes in about a minute and prints almost nothing worth reading. The trouble arrives later. aws --version reports a version that matches nothing anyone remembers installing, a command that worked last week now talks to the wrong account, and the region comes from a place that is not in any file that has been edited. None of that is Homebrew misbehaving. It is two separate facts sitting next to each other: the AWS CLI can be installed several ways on the same Mac, and it reads credentials from ten locations in a fixed order. Both are worth settling once.
What the formula actually puts on disk
The formula in homebrew-core is awscli, described as the official Amazon AWS command-line interface, licensed Apache-2.0, and currently at stable version 2.37.4. It tracks version 2 of the CLI, and its head points at the v2 branch of the aws-cli repository. Installation volume gives a sense of how well travelled this path is: the formula analytics on formulae.brew.sh report 104,450 installs in the last 30 days and 2,329,012 over the last year.
What the formula builds is not a copy of AWS's own bundle. It creates a Python virtual environment under the formula's libexec directory, installs the CLI and its Python dependencies into it, and links the aws entry point into the Homebrew prefix. The dependency list is explicit about this: [email protected] plus nine AWS C libraries, aws-c-auth, aws-c-cal, aws-c-common, aws-c-event-stream, aws-c-http, aws-c-io, aws-c-mqtt, aws-c-s3 and aws-checksums. The build is told to use the system libcrypto and the system AWS C libraries rather than vendoring its own, which is why those formulae appear as dependencies at all.
Two side effects of the formula are easy to miss. The first is in its caveat text, which reports that the examples directory has been installed to $HOMEBREW_PREFIX/share/awscli/examples. Those are the per-command example files that the CLI's own help system reads. The second is completion: the formula removes aws.cmd, aws_bash_completer and aws_zsh_completer.sh from bin, then installs the bash completer into Homebrew's bash completion directory and the zsh completer, along with a generated _aws function, into Homebrew's zsh completion directory. The Homebrew route is the only one of the install methods that puts completion files in place for you, which matters later.
One requirement is worth checking before any of this. AWS supports the CLI on macOS versions 11 and later, and it publishes a matrix: CLI 2.21.0 through the current release require macOS 11 or newer, 2.17.0 through 2.20.0 require 10.15 or newer, and 2.0.0 through 2.16.12 cover 10.14 and below. Homebrew's own macOS requirement is stricter on current hardware, with macOS Sequoia 15 or higher listed as the supported baseline on Apple Silicon.
Two copies of aws, and which one answers
The Homebrew prefix is /opt/homebrew on Apple Silicon and /usr/local on Intel Macs. AWS's installers put things in different places again. The install script, which AWS calls the recommended method, installs to $HOME/.local/share/aws-cli with a symlink in $HOME/.local/bin by default, and to /usr/local/aws-cli with a symlink in /usr/local/bin when passed --system. The GUI package installer defaults to /usr/local/aws-cli and creates a symlink at /usr/local/bin/aws.
On an Apple Silicon Mac those paths do not overwrite each other. They compete on PATH instead, which is a quieter kind of conflict. A Mac that once had the package installer and later got the formula now has two working aws binaries, and which one runs depends on the order that PATH was assembled by shell startup files.
| Install method | Where aws lives |
How it updates | Completion files |
|---|---|---|---|
| Homebrew formula | The Homebrew prefix's bin |
brew upgrade awscli |
Installed by the formula |
| AWS install script, per user | $HOME/.local/bin |
Rerun the script | Set up by hand |
AWS install script, --system |
/usr/local/bin |
Rerun the script with sudo |
Set up by hand |
| GUI or command line package | /usr/local/bin |
Rerun the installer | Set up by hand |
Two commands settle the question. type -a aws lists every match on PATH in the order the shell would use them, and aws --version prints a string of the form aws-cli/2.27.41 Python/3.11.6 Darwin/23.3.0. The Python component is the tell. A Homebrew build reports the Python version from the [email protected] formula it was linked against, while AWS's own bundle reports the Python it ships internally. If the version string and the version Homebrew thinks is installed disagree, the answer is almost always that a second copy is earlier on PATH.
Removing the unwanted one is better than reordering PATH around it. Deleting the symlink and the installation directory that AWS's installer created leaves the Homebrew copy as the only answer, and future confusion disappears with it.
Updating through Homebrew, and when that is the wrong tool
Once the formula owns the installation, brew upgrade awscli is the update. brew outdated awscli answers whether there is anything to do without doing it. The practical caveat is that the formula follows AWS's releases rather than leading them, and AWS is direct about not guaranteeing the currency of package managers it does not operate. The only third-party distribution channel AWS lists as officially supported is snap, which is a Linux mechanism and irrelevant on a Mac.
For most work that lag is measured in days and does not matter. It matters when a new service or a new parameter has just shipped and the CLI in the prefix does not have it yet. In that situation the useful move is not to fight the formula. It is to install AWS's own build alongside it, deliberately, and call it by its full path rather than adding it to PATH. That keeps one copy authoritative and one copy available.
brew pin awscli is the opposite case. It prevents the formula from being upgraded by brew upgrade, which is the tool for an environment where a specific CLI version is part of the contract. Homebrew notes that a pinned formula which another formula depends on still has to be upgraded when required, because building against outdated versions is not supported, so pinning is a preference rather than a guarantee.
Credentials load from ten places in a fixed order
This is where most of the lost time goes, and it has nothing to do with how the CLI was installed. AWS documents the resolution order plainly:
Credentials and configuration settings are located in multiple places, such as the system or user environment variables, local AWS configuration files, or explicitly declared on the command line as a parameter. Certain locations take precedence over others. Source: docs.aws.amazon.com
The order runs: command line options first, then environment variables, then an assumed IAM role, then assume role with web identity, then IAM Identity Center settings written by aws configure sso, then the credentials file, then a custom credential process, then the configuration file, then container credentials, and finally an EC2 instance profile. Ten places, and the first one that produces an answer wins.
Two files carry most of the local configuration. ~/.aws/credentials holds access keys and ~/.aws/config holds everything else, and both are written by aws configure. Their locations can be moved with AWS_CONFIG_FILE and AWS_SHARED_CREDENTIALS_FILE, and AWS notes that neither of those two settings can be expressed as a profile setting or as a command line parameter. They exist only as environment variables, which makes them a common cause of a shell that behaves differently from every other shell on the machine.
The region has its own small hierarchy worth memorising. AWS_REGION is the SDK compatible variable and it overrides both AWS_DEFAULT_REGION and the region setting in a profile. --region on the command line overrides all three. A Mac where one terminal tab exports AWS_DEFAULT_REGION and another exports AWS_REGION will produce two different answers from the same command, with no file on disk differing.
The command that ends the guessing
aws configure list is the tool for this, and it is underused. It prints each setting with its value, the type of source it came from, and the name of the variable or file behind it. Output looks like this:
Name Value Type Location
---- ----- ---- --------
profile <not set> None None
access_key ****************ABCD shared-credentials-file
secret_key ****************ABCD shared-credentials-file
region us-west-2 env AWS_DEFAULT_REGION
The Type and Location columns are the point. A region that came from an environment variable is labelled as such, and the variable is named. For temporary credentials obtained through a role or through IAM Identity Center, the command shows the cached key that is actually in use. aws configure list-profiles prints the profile names the CLI can see, which is the fastest way to confirm that a profile name is spelled the way the config file spells it.
Running this before editing any file changes the shape of the problem. Instead of guessing which of ten sources won, the answer is on screen. This is also the moment where working in one window pays off, because the result of the command and the contents of ~/.aws/config need to be read together rather than in two applications. A file manager with a built-in terminal exists to keep that pair side by side.
Completion is installed, but zsh needs one more line
Homebrew is explicit that it stores completions under the prefix and that the shell may not search there on its own:
Homebrew stores completions under HOMEBREW_PREFIX, which a system shell may not search automatically. The installer does not modify every shell's completion configuration because startup files and plugin managers vary. Source: docs.brew.sh
For zsh, brew shellenv adds Homebrew's zsh completion directory to FPATH, so the requirement is that eval "$(brew shellenv)" runs before zsh initialises completion. If the shell configuration does not already call compinit, adding autoload -Uz compinit followed by compinit to ~/.zshrc is what makes the formula's _aws function reachable. Oh My Zsh calls compinit itself when it loads, so the only requirement there is ordering.
For bash, Homebrew's documented snippet sources its completion loader from the prefix, and it requires either bash-completion for the system bash that macOS ships or bash-completion@2 for Homebrew's own bash. The two formulae conflict, so only one gets installed.
If zsh complains about insecure completion directories after this, compaudit lists which paths it objects to. The correct fix is narrow, and the next section explains why.
Profiles are the part worth setting up properly
Once more than one account is in play, named profiles do the work that separate shells and exported keys do badly. A profile is a named block in ~/.aws/config, selected with --profile on a single command or with AWS_PROFILE for a shell. AWS_PROFILE overrides the use of the [default] block, and --profile overrides AWS_PROFILE, which is the same precedence rule as everything else.
For organisations on IAM Identity Center, aws configure sso writes the profile into ~/.aws/config and aws sso login authenticates it. That combination is worth preferring over long-lived access keys in ~/.aws/credentials, because the credentials that reach disk are temporary and the login is a separate, visible step. The precedence list places IAM Identity Center settings above the credentials file, so a machine that has both will use the Identity Center profile, which is usually the intent.
Key rotation is the case where the file layout earns its keep. Because keys live in ~/.aws/credentials and nothing else does, replacing them is a single file edit, and aws configure list confirms afterwards that the value in use came from that file rather than from a stale environment variable in one long-running shell.
What to change first
Run type -a aws and aws configure list before changing anything, and remove the copy of the CLI that is not wanted rather than reordering PATH around it. Then move keys out of environment variables and into named profiles, so that the answer to which account a command hit is a file rather than a memory of which tab it was typed in. Reading that output next to the config file in one window is what a Finder replacement with a terminal built in is for, and Atriens is built around exactly that pairing.
Frequently asked questions
Should the AWS CLI be installed with Homebrew or with AWS's own installer?
Both work. Homebrew keeps the CLI in the same update loop as everything else on the machine and is the only method that installs shell completion files without extra steps. AWS's install script is the method AWS recommends and it ships the newest release first, since AWS does not maintain third-party repositories and does not guarantee their currency. Pick one and remove the other rather than running both.
Why does aws --version show a different version from the one Homebrew just installed?
Almost always because a second copy of aws sits earlier on PATH, usually a symlink in /usr/local/bin left by AWS's package installer. type -a aws lists every match in the order the shell uses them. The Python/ component of the version string also differs between the two builds, which confirms which one answered.
Where does the AWS CLI store credentials on a Mac?
Access keys go in ~/.aws/credentials and everything else in ~/.aws/config, and both are written by aws configure. Those paths can be changed with the AWS_CONFIG_FILE and AWS_SHARED_CREDENTIALS_FILE environment variables, which cannot be set as profile settings or as command line parameters.
A command is using the wrong region or account. How can the source be found?
aws configure list prints each setting with the type of source it came from and the name of the variable or file behind it. An environment variable is labelled as such and named, so a shell that exports AWS_DEFAULT_REGION or AWS_PROFILE is visible immediately rather than inferred.
Does brew upgrade awscli ever break a working setup?
The upgrade replaces the binary, not the contents of ~/.aws, so profiles and keys survive it. The risk is version specific behaviour in scripts. brew pin awscli holds a version in place, with the caveat that a pinned formula still has to be upgraded when another formula requires a newer version of it.
Is tab completion supposed to work straight after installing the formula?
The files are installed, but the shell has to be told to look for them. For zsh, eval "$(brew shellenv)" has to run before completion is initialised, and compinit has to be called by the configuration or by a framework such as Oh My Zsh. For bash, Homebrew's documented snippet plus either bash-completion or bash-completion@2 is required.