basecamp/kamal · error · RuntimeError
Failed to login to and unlock Bitwarden
Error message
Failed to login to and unlock Bitwarden
What it means
The Bitwarden adapter drives the bw CLI through a status → login → unlock cycle using the --account email; if the final `bw status` is still not "unlocked", it raises RuntimeError. Causes include a failed master-password prompt (login/unlock are interactive), wrong credentials, required 2FA, or an invalid BW_SESSION.
Source
Thrown at lib/kamal/secrets/adapters/bitwarden.rb:16
class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
private
def login(account)
status = run_command("status")
if status["status"] == "unauthenticated"
run_command("login #{account.shellescape}", raw: true)
status = run_command("status")
end
if status["status"] == "locked"
session = run_command("unlock --raw", raw: true).presence
status = run_command("status", session: session)
end
raise RuntimeError, "Failed to login to and unlock Bitwarden" unless status["status"] == "unlocked"
run_command("sync", session: session, raw: true)
raise RuntimeError, "Failed to sync Bitwarden" unless $?.success?
session
end
def fetch_secrets(secrets, from:, account:, session:)
{}.tap do |results|
items_fields(prefixed_secrets(secrets, from: from)).each do |item, fields|
item_json = run_command("get item #{item.shellescape}", session: session, raw: true)
raise RuntimeError, "Could not read #{item} from Bitwarden" unless $?.success?
item_json = JSON.parse(item_json)
if fields.any?
results.merge! fetch_secrets_from_fields(fields, item, item_json)
elsif item_json.dig("login", "password")
results[item] = item_json.dig("login", "password")
elsif item_json["fields"]&.any?View on GitHub (pinned to eee0083b38)
Solutions
- In an interactive shell, run bw status; if locked run bw unlock, export BW_SESSION, and retry — Kamal will then reuse the unlocked session
- For CI/non-interactive use, unlock once and export BW_SESSION as a secure CI variable so the adapter finds the vault already unlocked
- Make sure --account is the exact Bitwarden account email that is logged in (bw status shows it)
- If 2FA blocks scripted login, pre-provision BW_SESSION (bw unlock once) instead of letting Kamal log in
Example fix
# before (CI, non-interactive) kamal secrets fetch -a bitwarden --account me@example.com RAILS_MASTER_KEY # => RuntimeError: Failed to login to and unlock Bitwarden # fix (interactive once, then CI) bw unlock # prints: export BW_SESSION="xxx..." export BW_SESSION="xxx..." # or store as CI secret kamal secrets fetch -a bitwarden --account me@example.com RAILS_MASTER_KEY
Defensive patterns
Strategy: validation
Validate before calling
# Ensure the vault is usable non-interactively before invoking kamal
status = JSON.parse(`bw status 2>/dev/null`)
case status["status"]
when "unlocked" then nil
when "locked" then abort "run `bw unlock` and export BW_SESSION first"
else abort "run `bw login #{status.dig('user', 'email')}` first (or fix 2FA/session)"
end Type guard
def bitwarden_ready? JSON.parse(`bw status 2>/dev/null`)['status'] == 'unlocked' rescue StandardError false end
Try / catch
begin
adapter.fetch(names, account: account)
rescue RuntimeError => e
if e.message.include?("Failed to login to and unlock Bitwarden")
abort "Bitwarden locked: run `bw unlock`, export BW_SESSION, and retry"
end
raise
end Prevention
- In CI, store a pre-unlocked BW_SESSION as a secret variable instead of relying on interactive login
- Ensure the --account email matches the account bw is logged into (check bw status)
- Script a `bw status` pre-check in deploy pipelines for a clear, early error message
When it happens
Trigger: kamal secrets fetch -a bitwarden --account me@example.com ... where the interactive `bw login`/`bw unlock` prompt fails or is unavailable (non-interactive CI shell); account email not matching the Bitwarden account; 2FA challenge on login; a stale BW_SESSION env var so unlock returns an unusable key.
Common situations: Running kamal secrets fetch in CI where bw cannot prompt for the master password; bw logged into a different account than --account; 2FA enabled and no session pre-provisioned; expired session on a long-lived agent.
Related errors
- Bitwarden CLI is not installed
- You must specify what to retrieve from Bitwarden Secrets Man
- Could not read #{secret_uuid} from Bitwarden Secrets Manager
- Could not read secrets from Bitwarden Secrets Manager
- Could not authenticate to Bitwarden Secrets Manager. Did you
AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21).
Data as JSON: /api/errors/ab7fca7d28054a6e.
Report an issue: GitHub.