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

  1. 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.
  2. For CI, prefer a service-account key or Workload Identity: `gcloud auth activate-service-account --key-file=...`, which makes `gcloud auth list` non-empty.
  3. Confirm the --account value matches a credential listed by `gcloud auth list` (email must be exact).
  4. 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

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


AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21). Data as JSON: /api/errors/da2704da73291abe. Report an issue: GitHub.