Setting four environment variables every time you switch client directories gets old fast. direnv solves this by loading environment variables automatically when you enter a directory and unloading them when you leave. Combined with the four-variable isolation pattern, this means each client’s credentials load when you cd into their project and disappear when you leave.
How direnv Works
direnv is a shell extension that watches for .envrc files. When you navigate into a directory containing an .envrc, direnv sources it and sets the declared environment variables in your shell. When you navigate out, it reverses the changes.
The first time you enter a directory with a new .envrc, direnv requires you to explicitly trust it (direnv allow). After that, it loads automatically. This safety step prevents arbitrary code execution from cloned repositories.
The .envrc Pattern
A complete .envrc for a single client project looks like this:
export CLOUDSDK_CONFIG="$HOME/.config/gcloud-acme"export CLOUDSDK_CORE_PROJECT=acme-analytics-prodexport GOOGLE_CLOUD_PROJECT=acme-analytics-prodexport GOOGLE_APPLICATION_CREDENTIALS="$HOME/.gcp-keys/acme-dbt-runner.json"
# dbt-specificexport DBT_TARGET=prodexport DBT_PROFILES_DIR="$HOME/.dbt"When you cd into ~/projects/acme-analytics/, direnv loads these variables. Your gcloud CLI, Python client libraries, dbt, and anything else reading standard GCP environment variables will use the Acme credentials. When you cd out, they’re unloaded.
Every client directory gets its own .envrc with its own values. Switching clients is just switching directories.
Why Four Variables
Two pairs of variables handle two independent systems:
gcloud CLI pair:
CLOUDSDK_CONFIG— the directory containing all gcloud state (credentials, config, ADC file, logs)CLOUDSDK_CORE_PROJECT— the default project for gcloud commands
Client library pair:
GOOGLE_APPLICATION_CREDENTIALS— path to a service account key file for ADCGOOGLE_CLOUD_PROJECT— the default project for Python, dbt, Terraform, and other client libraries
Skipping either pair means something targets the wrong project. A setup with only the gcloud pair will have gcloud pointing at the right client while dbt run uses whatever ADC happens to be set globally. A setup with only the client library pair does the reverse. See GCP Application Default Credentials for the full picture on why these two systems are separate.
Setting Up a New Client
The full setup for a new client takes about five minutes:
# 1. Create isolated gcloud config directorymkdir -p ~/.config/gcloud-newclient
# 2. Activate the service account in that isolated directoryCLOUDSDK_CONFIG=~/.config/gcloud-newclient \ gcloud auth activate-service-account \ --key-file=~/.gcp-keys/newclient-sa.json
# 3. Create the .envrccat > ~/projects/newclient-dbt/.envrc << 'EOF'export CLOUDSDK_CONFIG="$HOME/.config/gcloud-newclient"export CLOUDSDK_CORE_PROJECT=newclient-warehouseexport GOOGLE_CLOUD_PROJECT=newclient-warehouseexport GOOGLE_APPLICATION_CREDENTIALS="$HOME/.gcp-keys/newclient-sa.json"export DBT_TARGET=prodEOF
# 4. Trust the .envrcdirenv allow ~/projects/newclient-dbtAfter this, navigating into the project directory automatically provides the right credentials with no further action required.
AI Agents Inherit These Variables
A particularly useful property: Claude Code, Cursor, and other IDE-based agents inherit environment variables from the shell that launched them. If you start Claude Code from inside a project directory where direnv has loaded the .envrc, the agent gets the right GCP context automatically.
This means an agent running gcloud bq query or triggering a dbt build will use the correct client’s credentials without any extra configuration. The agent doesn’t need to know about the credential setup — it’s ambient, from the shell.
The limitation is that agents can’t refresh credentials when they expire. If a session runs long enough that tokens expire, the agent fails with auth errors and you need to restart. See AI Agent GCP Auth Constraints for the specific behavior of different agents around credential expiry.
Installation
direnv is available through most package managers:
# macOSbrew install direnv
# Then add to your shell config (.zshrc or .bashrc):eval "$(direnv hook zsh)" # for zsheval "$(direnv hook bash)" # for bashWhat Gets Unloaded
When you navigate out of a direnv-managed directory, all exported variables from that .envrc are removed from your shell environment. This means:
CLOUDSDK_CONFIGreverts to unset (gcloud goes back to~/.config/gcloud/)GOOGLE_APPLICATION_CREDENTIALSreverts to unsetGOOGLE_CLOUD_PROJECTreverts to unset
If you have a global fallback set in your shell profile (.zshrc), those values resume. If you have nothing set globally, the environment is clean with no GCP context. Either behavior is predictable, which is the point.
Keeping Key Files Safe
The .envrc contains paths to key files but not the key files themselves. Keep the actual key files in ~/.gcp-keys/ with tight permissions and excluded from cloud sync. The .envrc files can safely live in version control since they only reference paths.
chmod 600 ~/.gcp-keys/newclient-sa.jsonThe .envrc itself should usually be in .gitignore since it contains client-specific configuration. Or keep it in version control if you want the project to be self-documenting — the paths in .envrc only matter on your local machine anyway.