basecamp/kamal · error · RuntimeError
Failed to login to LastPass
Error message
Failed to login to LastPass
What it means
Raised in LastPass#login when `lpass login <account>` exits non-zero. The adapter first checks loggedin? by comparing `lpass status --color never` output to "Logged in as <account>."; if that exact string does not match, it attempts an interactive lpass login, which requires a TTY and pinentry for the master password. Any mismatch or non-interactive failure triggers this RuntimeError.
Source
Thrown at lib/kamal/secrets/adapters/last_pass.rb:6
class Kamal::Secrets::Adapters::LastPass < Kamal::Secrets::Adapters::Base
private
def login(account)
unless loggedin?(account)
`lpass login #{account.shellescape}`
raise RuntimeError, "Failed to login to LastPass" unless $?.success?
end
end
def loggedin?(account)
`lpass status --color never`.strip == "Logged in as #{account}."
end
def fetch_secrets(secrets, from:, account:, session:)
secrets = prefixed_secrets(secrets, from: from)
items = `lpass show #{secrets.map(&:shellescape).join(" ")} --json`
raise RuntimeError, "Could not read #{secrets} from LastPass" unless $?.success?
items = JSON.parse(items)
{}.tap do |results|
items.each do |item|
results[item["fullname"]] = item["password"]
endView on GitHub (pinned to eee0083b38)
Solutions
- Log in once interactively on the machine as the SAME account: `LPASS_DISABLE_PINENTRY=1 lpass login user@example.com` (reads password from stdin) or normal `lpass login`, so `lpass status --color never` prints exactly 'Logged in as user@example.com.'.
- Ensure --account matches the logged-in account byte-for-byte (the adapter's loggedin? does an exact string compare including the trailing period).
- For automation, use lpass's non-interactive login: `echo "$LPASS_PASSWORD" | LPASS_DISABLE_PINENTRY=1 lpass login user@example.com` in a pre-step before kamal runs.
- Install/verify pinentry if interactive login aborts (brew install pinentry, set LPASS_PINENTRY).
Example fix
# before: CI has no lpass session # -> RuntimeError: Failed to login to LastPass # after: non-interactive pre-login step in CI - run: echo "$LPASS_PASSWORD" | LPASS_DISABLE_PINENTRY=1 lpass login team@company.com - run: kamal secrets pull --account team@company.com
Defensive patterns
Strategy: try-catch
Validate before calling
require "open3"
def lpass_logged_in_as?(account)
out, _err, _st = Open3.capture3("lpass", "status", "--color", "never")
out.strip == "Logged in as #{account}."
end
abort "Not logged into LastPass as #{ACCOUNT}" unless lpass_logged_in_as?(ACCOUNT) Try / catch
begin
secrets = adapter.fetch(names, account: ACCOUNT)
rescue RuntimeError => e
if e.message == "Failed to login to LastPass"
raise "lpass login failed: pre-authenticate (LPASS_DISABLE_PINENTRY=1 lpass login #{ACCOUNT}) and re-run"
end
raise
end Prevention
- Log in non-interactively in a pre-step: `echo "$LPASS_PASSWORD" | LPASS_DISABLE_PINENTRY=1 lpass login <account>`.
- Ensure the --account string exactly matches `lpass status` output (adapter compares the full sentence).
- In CI, inject LPASS_PASSWORD as a secret and never assume a cached ~/.lpass session.
When it happens
Trigger: adapter.fetch(secrets, account: 'user@example.com') when: not logged in at all (lpass status shows 'Not logged in'); logged in as a DIFFERENT account than --account (string mismatch causes a pointless re-login attempt); lpass login cannot prompt (no TTY/pinentry in CI/ssh); or the master password entry fails/aborts. Note $?.success? is checked after the backtick, so a failed or cancelled login raises.
Common situations: CI containers with no pinentry and no cached session (~/.lpass); account email typo or changed corporate email so status string never matches; trusted-machine session expired; running kamal from a script where lpass cannot open the prompt and exits non-zero.
Related errors
- could not login to gcloud
- Could not authenticate to Bitwarden Secrets Manager. Did you
- Failed to login to Doppler
- Could not read #{secrets} from LastPass
- Failed to login to Passbolt
AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21).
Data as JSON: /api/errors/c33c0be6f9493202.
Report an issue: GitHub.