Avatar
Mostly just a place where I dump interesting things I've learned that I'm pretty sure I'll forget.. It's my own personal knowledge base.

shell path caching and the hash -r command

About

I ran into a confusing issue recently where I had installed Java via Homebrew, my $PATH was correctly configured, but the java command kept pointing to the wrong version. The solution turned out to be a shell feature I’d never heard of: command path caching. This post explains what happened and how hash -r saved the day.

The Problem

After installing OpenJDK 17 via Homebrew and running brew link openjdk@17, I expected everything to work. My $PATH was configured correctly with /opt/homebrew/bin before /usr/bin:

$ echo $PATH
/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

But when I checked which java command was being used, it was still pointing to the macOS stub:

$ which java
/usr/bin/java

$ java -version
The operation couldn't be completed. Unable to locate a Java Runtime...

This made no sense! The correct java binary existed at /opt/homebrew/bin/java, and that path was first in my $PATH. Why wasn’t the shell finding it?

The Culprit: Shell Command Caching

It turns out that shells like Bash and Zsh don’t search your entire $PATH every single time you run a command. Instead, they maintain an in-memory hash table that caches the full path to each command the first time you run it.

Here’s how it works:

  1. First time you run java: The shell searches through each directory in $PATH (from left to right), finds /usr/bin/java, and caches that location.
  2. Every subsequent time: The shell uses the cached location - no PATH search needed.

This is a performance optimization… searching the filesystem is expensive, so caching the results makes sense. But it causes problems when:

  • You install a new version of a command
  • You change your $PATH order
  • You run brew link to create new symlinks
  • You install global npm/pip packages

In my case, the shell had cached java → /usr/bin/java before I installed Homebrew’s Java. Even though /opt/homebrew/bin/java now existed and should have taken precedence, the shell kept using the stale cached location.

The Solution: hash -r

The hash -r command tells your shell to clear all cached command locations:

$ hash -r

$ which java
/opt/homebrew/bin/java

$ java -version
openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment Homebrew (build 17.0.16+0)
OpenJDK 64-Bit Server VM Homebrew (build 17.0.16+0, mixed mode, sharing)

Perfect! After clearing the cache, the shell re-searched $PATH and found the correct Java installation.

Inspecting the Hash Table

You can see what’s currently cached in your shell:

$ hash
hits    command
   5    /bin/ls
   2    /usr/bin/git
   1    /opt/homebrew/bin/npm

The “hits” column shows how many times you’ve used each command in this session.

To see a specific command’s cached path:

$ hash -v java
hash: java=/opt/homebrew/bin/java

To clear just one command (instead of the entire cache):

$ hash -d java

Shell Differences: Bash vs Zsh

Bash:

  • Uses the hash command
  • Usually auto-clears the cache when $PATH changes
  • hash -r clears all cached commands

Zsh:

  • Uses both hash and rehash (they’re synonyms)
  • More aggressive caching - doesn’t always auto-clear on PATH changes
  • rehash is the “zsh way” to say hash -r

If you’re using Zsh (which is the default on modern macOS), you might encounter this issue more often than Bash users.

Other Ways to Bypass the Cache

If you don’t want to clear the entire cache, you can force a fresh PATH lookup:

# Use the full path directly
/opt/homebrew/bin/java -version

# Use 'command -v' to bypass the cache
command -v java

You can also check if the cache is causing your issue:

# These should match:
which java
command -v java

# If they're different → your cache is stale!

When to Use hash -r

Run hash -r (or rehash in Zsh) after:

  • Installing software via Homebrew: brew install something && hash -r
  • Changing your $PATH: export PATH="/new/path:$PATH" && hash -r
  • Installing global npm packages: npm install -g some-tool && hash -r
  • Updating dotfiles that modify $PATH: source ~/.zshrc && hash -r

Or just open a new terminal - fresh shells start with an empty cache!

Final Thoughts

The shell’s command caching is a clever performance optimization that works great… until it doesn’t. When you install new software or change your environment, stale cache entries can cause confusing behavior where which shows the wrong path despite your $PATH being correct.

Now you know: when commands aren’t being found where you expect them, try hash -r before diving deeper into troubleshooting. It’s saved me hours of debugging time, and hopefully it’ll save you some too!

all tags