basecamp/kamal · error · RuntimeError
could not login to gcloud
Error message
could not login to gcloud
What it means
Raised in GcpSecretManager#login when, after `gcloud auth login` returns, `gcloud auth list --format=json` still parses to an empty array (logged_in? is false). The adapter supports accounts like 'user@example.com', 'default', or 'user|service-account' impersonation chains; the interactive `gcloud auth login` needs a browser and a real TTY, so headless environments fail the post-login recheck.
Source
Thrown at lib/kamal/secrets/adapters/gcp_secret_manager.rb:23
# impersonation.
#
# Syntax:
# ACCOUNT: USER | USER "|" DELEGATION_CHAIN
# USER: DEFAULT_USER | EMAIL
# DELEGATION_CHAIN: EMAIL | EMAIL "," DELEGATION_CHAIN
# EMAIL: <The email address of the user or service account, like "my-user@example.com" >
# DEFAULT_USER: "default"
#
# Some valid examples:
# - "my-user@example.com" sets the user
# - "my-user@example.com|my-service-user@example.com" will use my-user and enable service account impersonation as my-service-user
# - "default" will use the default user and no impersonation
# - "default|my-service-user@example.com" will use the default user, and enable service account impersonation as my-service-user
# - "default|my-service-user@example.com,another-service-user@example.com" same as above, but with an impersonation delegation chain
unless logged_in?
`gcloud auth login`
raise RuntimeError, "could not login to gcloud" unless logged_in?
end
nil
end
def fetch_secrets(secrets, from:, account:, session:)
user, service_account = parse_account(account)
{}.tap do |results|
secrets_with_metadata(prefixed_secrets(secrets, from: from)).each do |secret, (project, secret_name, secret_version)|
item_name = "#{project}/#{secret_name}"
results[item_name] = fetch_secret(project, secret_name, secret_version, user, service_account)
raise RuntimeError, "Could not read #{item_name} from Google Secret Manager" unless $?.success?
end
end
end
def fetch_secret(project, secret_name, secret_version, user, service_account)View on GitHub (pinned to eee0083b38)
Solutions
- Authenticate out-of-band once on the machine: `gcloud auth login` in an interactive shell (or `gcloud auth login --no-launch-browser` and paste the URL), so logged_in? passes and the adapter never triggers its own login.
- For CI, prefer a service-account key or Workload Identity: `gcloud auth activate-service-account --key-file=...`, which makes `gcloud auth list` non-empty.
- Confirm the --account value matches a credential listed by `gcloud auth list` (email must be exact).
- If you only need secret reads, grant the runtime identity roles/secretmanager.secretAccessor and run kamal from an environment with ADC (GOOGLE_APPLICATION_CREDENTIALS) instead of user login.
Example fix
# before: headless box, adapter shells out to `gcloud auth login` # -> RuntimeError: could not login to gcloud # after: pre-authenticate with a service account in provisioning gcloud auth activate-service-account --key-file=/secure/kamal-sa.json kamal secrets pull --account=default
Defensive patterns
Strategy: try-catch
Validate before calling
require "open3"
require "json"
def gcloud_logged_in?
out, _err, status = Open3.capture3("gcloud", "auth", "list", "--format=json")
status.success? && JSON.parse(out).any?
end
abort "No gcloud credentials: run `gcloud auth login` or activate a service account" unless gcloud_logged_in? Try / catch
begin
secrets = adapter.fetch(%w[myproj/RAILS_MASTER_KEY], account: "default")
rescue RuntimeError => e
if e.message == "could not login to gcloud"
raise "gcloud auth unavailable headless — pre-run `gcloud auth login --no-launch-browser` or activate a SA key"
end
raise
end Prevention
- Provision credentials before kamal runs: activate a service account in CI (`gcloud auth activate-service-account --key-file`) instead of relying on interactive login.
- Use `--account=default` only where a valid ADC/user credential is known to exist.
- Add a preflight `gcloud auth list --format=json` check that fails with an actionable message.
When it happens
Trigger: adapter.fetch(secrets, account: 'me@example.com', ...) when no active gcloud credentials exist: `gcloud auth login` is spawned; it fails or cannot complete (no browser, no TTY, user aborts, X/ssh forwarding missing), then logged_in? is still false and the RuntimeError fires.
Common situations: Deploy boxes/CI with no Application Default Credentials and no way to open a browser; account shorthand passed with --account never having run `gcloud auth login` locally; ssh sessions without -X or without a local browser; corporate SSO where login requires a device flow the plain command cannot drive.
Related errors
- Failed to login to LastPass
- Could not authenticate to Bitwarden Secrets Manager. Did you
- Failed to login to Doppler
- gcloud CLI is not installed
- Failed to login to Passbolt
AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21).
Data as JSON: /api/errors/da2704da73291abe.
Report an issue: GitHub.