gcenv
Per-terminal and per-Claude-session GCP profile isolation. A gcloud config switcher that doesn't clobber your other tabs, your other scripts, or the agent running in the next window.
~/my-project main ❯ ☁️ prod
The problem
gcloud keeps one active account, one active project, and one Application Default Credentials file — globally, machine-wide. The moment you work on more than one GCP account, the manual flow turns into a quiet minefield:
# Morning: working on Client A
gcloud auth login [email protected]
gcloud config set account [email protected]
gcloud config set project client-a-prod
gcloud auth application-default login --billing-project=client-a-prod
# → ~/.config/gcloud/application_default_credentials.json now points at A
# Slack ping from Client B — open new tab, "real quick"
gcloud config set account [email protected]
gcloud config set project client-b-staging
# → both tabs now silently target B. The first tab doesn't know.
# Run the deploy script in tab 1, expecting A
./deploy.sh
# → deployed to B. Cue the chest tightening.
# Switch back: re-auth, re-set, re-ADC-login (browser dance)
gcloud config set account [email protected]
gcloud config set project client-a-prod
gcloud auth application-default login --billing-project=client-a-prod
The footguns are silent:
gcloud config set is a global mutation. Every tab, every IDE integration, every Python script in venv #4 reads the same active config.
- ADC lives at one path:
~/.config/gcloud/application_default_credentials.json. The last gcloud auth application-default login wins for every process on the machine — including a long-running script in another tab that suddenly switches accounts mid-execution.
CLOUDSDK_BILLING_QUOTA_PROJECT is easy to forget. Without it, ADC user-account calls bill quota to the wrong project (and sometimes 403 against serviceusage.services.use).
- Two terminals. Same repo. Same
gcloud binary. Different intent. Nothing tells you which is which.
What gcenv does instead
Each terminal tab gets its own GCP context — set once, persists for that tab's lifetime, invisible to every other tab.
gcenv use client-a # this tab is now Client A. Forever, until you change it.
# Other tab:
gcenv use client-b # that tab is Client B. The first tab is unaffected.
Mechanism: gcenv exports CLOUDSDK_CORE_ACCOUNT, CLOUDSDK_CORE_PROJECT, CLOUDSDK_BILLING_QUOTA_PROJECT, and GOOGLE_APPLICATION_CREDENTIALS — all per-shell environment variables that gcloud and Google client libraries already honor. The global gcloud config is never touched. Each profile gets its own ADC file (~/.gcenv/adc/<profile>.json), so two Python scripts in two tabs run against two different accounts simultaneously, with zero risk of crossover. The active profile shows in your prompt. Stale auth tokens are detected on switch and refreshed once, interactively.
How does this compare to other tools?
| per-terminal isolation | per-profile ADC | per-Claude-session | activity |
|---|
| gcenv | ✅ | ✅ | ✅ | this repo |
gcloud config configurations (built-in) | ❌ global | ❌ single global ADC | ❌ | official |
| tjirsch/gcloud-switch (Rust TUI) | ❌ activates globally | stores per-profile ADC but copies into the single global path on activate | ❌ | 0 ⭐ |
| xgourmandin/gcloud-switch (Go) | ❌ global | ✅ reuses if valid | ❌ | 2 ⭐ |
| sakebook/gsw | ✅ via CLOUDSDK_ACTIVE_CONFIG_NAME | ❌ shared global file | ❌ | 2 ⭐ |
| ogerbron/gcloudctx | ❌ global (gcloud config configurations activate) | ❌ no ADC management | ❌ | 5 ⭐ |
| direnv | ✅ but per-directory, not per-tab | ✅ if you wire it | ❌ | mature |
No actively-maintained tool combines per-terminal isolation and per-profile ADC files before this one. The combination is what makes simultaneous multi-account work safe — running terraform apply against Client A in tab 1 while a Python script hits Client B's BigQuery in tab 2, with no shared state to fight over.
Why not gcloud config configurations?
gcloud config configurations activate prod writes the active configuration to ~/.config/gcloud/active_config — a single file every shell on the machine reads. Two tabs cannot be on different configurations at the same time. The whole reason gcenv exists is that this is wrong.
Why not direnv?