{"record":{"id":"cde5845eaa3812dd","repo":"basecamp/kamal","slug":"failed-to-login-to-doppler","errorCode":null,"errorMessage":"Failed to login to Doppler","messagePattern":"Failed to login to Doppler","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"lib/kamal/secrets/adapters/doppler.rb","lineNumber":10,"sourceCode":"class Kamal::Secrets::Adapters::Doppler < Kamal::Secrets::Adapters::Base\n  def requires_account?\n    false\n  end\n\n  private\n    def login(*)\n      unless loggedin?\n        `doppler login -y`\n        raise RuntimeError, \"Failed to login to Doppler\" unless $?.success?\n      end\n    end\n\n    def loggedin?\n      `doppler me --json 2> /dev/null`\n      $?.success?\n    end\n\n    def fetch_secrets(secrets, from:, **)\n      secrets = prefixed_secrets(secrets, from: from)\n      flags = secrets_get_flags(secrets)\n\n      secret_names = secrets.collect { |s| s.split(\"/\").last }\n\n      items = `doppler secrets get #{secret_names.map(&:shellescape).join(\" \")} --json #{flags}`\n      raise RuntimeError, \"Could not read #{secrets} from Doppler\" unless $?.success?\n\n      items = JSON.parse(items)","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/basecamp/kamal/blob/eee0083b38661c3707c6b6052cc89e85038a096c/lib/kamal/secrets/adapters/doppler.rb#L1-L28","documentation":"Kamal's Doppler adapter raises this in #login when `doppler login -y` exits non-zero. The adapter first runs `doppler me --json 2> /dev/null` (loggedin?); if that probe fails it attempts an interactive browser login, which cannot complete in non-interactive shells. Note Doppler marks requires_account? false, so no --account option is needed — the failure is purely CLI authentication.","triggerScenarios":"adapter.fetch(...) with no valid local Doppler session AND no usable service token: `doppler me --json` fails (not logged in, token invalid), then `doppler login -y` runs and fails because it needs a browser/TTY (CI, ssh without forwarding, Docker build) or the login flow errors out.","commonSituations":"CI pipelines or deploy containers with no doppler session persisted; DOPPLER_TOKEN set to a personal (dp.pt...) or CLI token instead of a service token so `doppler me` still fails; expired local token in ~/.doppler; running kamal over a non-interactive ssh session.","solutions":["Set a Doppler service token so no interactive login is ever attempted: export DOPPLER_TOKEN=dp.st.xxxx (the adapter detects the dp.st prefix via service_token_set? and skips project flags/login needs).","Or log in once interactively on the machine (`doppler login`) so `doppler me --json` succeeds and the adapter's loggedin? short-circuits the login path.","If DOPPLER_TOKEN is set but not dp.st-prefixed, replace it with a service token created via `doppler configs tokens create` in the Doppler dashboard.","In CI, prefer injecting DOPPLER_TOKEN as a masked variable rather than relying on cached sessions."],"exampleFix":"# before: no session, interactive login fails in CI\n#   -> RuntimeError: Failed to login to Doppler\n\n# after: use a service token (adapter treats dp.st.* as service token)\nexport DOPPLER_TOKEN=\"dp.st.1.xxxx\"\nkamal secrets pull","handlingStrategy":"try-catch","validationCode":"require \"open3\"\n\ndef doppler_ready?\n  return true if ENV[\"DOPPLER_TOKEN\"]&.start_with?(\"dp.st.\") # service token: no interactive login\n  _out, _err, status = Open3.capture3(\"doppler\", \"me\", \"--json\")\n  status.success?\nend\n\nabort \"No Doppler session: set DOPPLER_TOKEN (dp.st.*) or run `doppler login`\" unless doppler_ready?","typeGuard":null,"tryCatchPattern":"begin\n  secrets = adapter.fetch(%w[RAILS_MASTER_KEY])\nrescue RuntimeError => e\n  if e.message == \"Failed to login to Doppler\"\n    raise \"Doppler login failed (headless?): export DOPPLER_TOKEN=dp.st.* or pre-run `doppler login`\"\n  end\n  raise\nend","preventionTips":["Always inject a Doppler service token (dp.st.*) in CI instead of relying on interactive login.","Probe `doppler me --json` in a pre-deploy step; mirror of the adapter's own loggedin? check with a clearer error.","Never assume a local ~/.doppler session exists on fresh runners."],"tags":["ruby","kamal","doppler","authentication","secrets","ci","service-token","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"}