{"record":{"id":"da2704da73291abe","repo":"basecamp/kamal","slug":"could-not-login-to-gcloud","errorCode":null,"errorMessage":"could not login to gcloud","messagePattern":"could not login to gcloud","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"lib/kamal/secrets/adapters/gcp_secret_manager.rb","lineNumber":23,"sourceCode":"      # impersonation.\n      #\n      # Syntax:\n      # ACCOUNT: USER | USER \"|\" DELEGATION_CHAIN\n      # USER: DEFAULT_USER | EMAIL\n      # DELEGATION_CHAIN: EMAIL | EMAIL \",\" DELEGATION_CHAIN\n      # EMAIL: <The email address of the user or service account, like \"my-user@example.com\" >\n      # DEFAULT_USER: \"default\"\n      #\n      # Some valid examples:\n      # - \"my-user@example.com\" sets the user\n      # - \"my-user@example.com|my-service-user@example.com\" will use my-user and enable service account impersonation as my-service-user\n      # - \"default\" will use the default user and no impersonation\n      # - \"default|my-service-user@example.com\" will use the default user, and enable service account impersonation as my-service-user\n      # - \"default|my-service-user@example.com,another-service-user@example.com\" same as above, but with an impersonation delegation chain\n\n      unless logged_in?\n        `gcloud auth login`\n        raise RuntimeError, \"could not login to gcloud\" unless logged_in?\n      end\n\n      nil\n    end\n\n    def fetch_secrets(secrets, from:, account:, session:)\n      user, service_account = parse_account(account)\n\n      {}.tap do |results|\n        secrets_with_metadata(prefixed_secrets(secrets, from: from)).each do |secret, (project, secret_name, secret_version)|\n          item_name = \"#{project}/#{secret_name}\"\n          results[item_name] = fetch_secret(project, secret_name, secret_version, user, service_account)\n          raise RuntimeError, \"Could not read #{item_name} from Google Secret Manager\" unless $?.success?\n        end\n      end\n    end\n\n    def fetch_secret(project, secret_name, secret_version, user, service_account)","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/secrets/adapters/gcp_secret_manager.rb#L5-L41","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: headless box, adapter shells out to `gcloud auth login`\n#   -> RuntimeError: could not login to gcloud\n\n# after: pre-authenticate with a service account in provisioning\ngcloud auth activate-service-account --key-file=/secure/kamal-sa.json\nkamal secrets pull --account=default","handlingStrategy":"try-catch","validationCode":"require \"open3\"\nrequire \"json\"\n\ndef gcloud_logged_in?\n  out, _err, status = Open3.capture3(\"gcloud\", \"auth\", \"list\", \"--format=json\")\n  status.success? && JSON.parse(out).any?\nend\n\nabort \"No gcloud credentials: run `gcloud auth login` or activate a service account\" unless gcloud_logged_in?","typeGuard":null,"tryCatchPattern":"begin\n  secrets = adapter.fetch(%w[myproj/RAILS_MASTER_KEY], account: \"default\")\nrescue RuntimeError => e\n  if e.message == \"could not login to gcloud\"\n    raise \"gcloud auth unavailable headless — pre-run `gcloud auth login --no-launch-browser` or activate a SA key\"\n  end\n  raise\nend","preventionTips":["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."],"tags":["ruby","kamal","gcloud","gcp","secrets","authentication","headless","cli-exit-code"],"backgroundTag":"cli-authentication-failed","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}