{"record":{"id":"a3d6fc96514ac63a","repo":"basecamp/kamal","slug":"could-not-read-item-name-from-google-secret-man","errorCode":null,"errorMessage":"Could not read #{item_name} from Google Secret Manager","messagePattern":"Could not read #(.+?) from Google Secret Manager","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"lib/kamal/secrets/adapters/gcp_secret_manager.rb","lineNumber":36,"sourceCode":"      # - \"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)\n      secret = run_command(\n        \"secrets versions access #{secret_version.shellescape} --secret=#{secret_name.shellescape}\",\n        project: project,\n        user: user,\n        service_account: service_account\n      )\n      Base64.decode64(secret.dig(\"payload\", \"data\"))\n    end\n\n    # The secret needs to at least contain a secret name, but project name, and secret version can also be specified.\n    #\n    # The string \"default\" can be used to refer to the default project configured for gcloud.\n    #","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/secrets/adapters/gcp_secret_manager.rb#L18-L54","documentation":"Raised inside the fetch loop of GcpSecretManager#fetch_secrets: for each requested secret it runs `gcloud secrets versions access <version> --secret=<name> --project=... [--account=...] [--impersonate-service-account=...] --format=json` (fetch_secret) and checks $?.success? afterwards. A non-zero exit for any single item (keyed project/secret_name) aborts the whole pull with this message. Note fetch_secret also JSON.parses the output, so some failures surface as JSON::ParserError before this raise.","triggerScenarios":"adapter.fetch(...) after successful auth when: the secret name does not exist in the project; the version alias (e.g. 'latest' or a numeric version) is destroyed/never existed; the authenticated user or impersonated service account lacks secretmanager.versions.access (IAM); the project flag resolved from the secret prefix is wrong; or the impersonation chain (user|sa1,sa2) is misconfigured/unauthorized.","commonSituations":"deploy.yml referencing GCP secrets by a path whose project slug differs from the real project id; secret created in a different region/replica set; developer account works locally but CI service account lacks the Secret Manager Accessor role; impersonation chain where an intermediate SA dropped the token creator role.","solutions":["Reproduce with the CLI to see the true error: `gcloud secrets versions access latest --secret=NAME --project=PROJ` (add --account/--impersonate-service-account as passed to kamal).","Grant IAM: `gcloud secrets add-iam-policy-binding NAME --member='serviceAccount:SA' --role='roles/secretmanager.secretAccessor'` for every identity in the chain (including impersonated ones).","Fix the name: confirm the secret exists via `gcloud secrets list --project=PROJ` and correct the prefix/name in your kamal secrets list.","If using impersonation, also grant roles/iam.serviceAccountTokenCreator on each target SA to the preceding identity."],"exampleFix":"# before: CI service account lacks read on the secret\n#   -> RuntimeError: Could not read myproj/RAILS_MASTER_KEY from Google Secret Manager\n\n# after: grant Secret Accessor\ngcloud secrets add-iam-policy-binding RAILS_MASTER_KEY \\\n  --project=myproj \\\n  --member=serviceAccount:deployer@myproj.iam.gserviceaccount.com \\\n  --role=roles/secretmanager.secretAccessor","handlingStrategy":"try-catch","validationCode":"require \"open3\"\n\ndef gcp_secret_readable?(project, name, account: \"default\")\n  _out, _err, status = Open3.capture3(\"gcloud\", \"secrets\", \"versions\", \"access\", \"latest\",\n                                      \"--secret=#{name}\", \"--project=#{project}\", \"--account=#{account}\")\n  status.success?\nend\n\nmissing = wanted.reject { |p, n| gcp_secret_readable?(p, n) }\nabort \"Unreadable secrets (IAM or missing): #{missing.inspect}\" if missing.any?","typeGuard":null,"tryCatchPattern":"begin\n  adapter.fetch(names, account: account)\nrescue RuntimeError => e\n  if (m = e.message.match(/Could not read (.+) from Google Secret Manager/))\n    raise \"GCP read failed for #{m[1]}: check name, project, and roles/secretmanager.secretAccessor for #{account}\"\n  end\n  raise\nend","preventionTips":["Grant roles/secretmanager.secretAccessor to every identity in the chain (user + impersonated SAs) at project or secret level.","Pre-verify each secret with `gcloud secrets describe` in a preflight script before deploying.","Keep the project/secret prefixes in deploy.yml aligned with real project IDs returned by `gcloud projects list`."],"tags":["ruby","kamal","gcp","secret-manager","secrets","iam","permissions","secret-not-found"],"backgroundTag":"secret-fetch-failed","analyzedSha":"eee0083b38661c3707c6b6052cc89e85038a096c","analyzedAt":"2026-08-21T15:17:22.045Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}