basecamp/kamal · error · RuntimeError
Failed to sync Bitwarden
Error message
Failed to sync Bitwarden
What it means
After a successful login/unlock, the Bitwarden adapter runs `bw sync` to pull the latest vault; if that command exits non-zero ($?.success? false), Kamal raises RuntimeError "Failed to sync Bitwarden". Sync is required so subsequent `bw get item` calls see current vault contents.
Source
Thrown at lib/kamal/secrets/adapters/bitwarden.rb:19
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?
fields = item_json["fields"].pluck("name")
results.merge! fetch_secrets_from_fields(fields, item, item_json)
elseView on GitHub (pinned to eee0083b38)
Solutions
- Retry the kamal secrets fetch — sync failures are often transient
- Run bw sync manually to see the underlying error; if it reports an invalid session, bw unlock again and re-export BW_SESSION
- Check connectivity/health of api.bitwarden.com (or your self-hosted server) and system clock accuracy
- Re-login from scratch (bw logout && bw login) if the account state is corrupted
Example fix
# before kamal secrets fetch -a bitwarden --account me@example.com RAILS_MASTER_KEY # => RuntimeError: Failed to sync Bitwarden # fix (terminal) bw sync # reproduce / see error bw unlock # refresh session if invalid export BW_SESSION=... kamal secrets fetch -a bitwarden --account me@example.com RAILS_MASTER_KEY
Defensive patterns
Strategy: retry
Validate before calling
abort "bitwarden unreachable / not synced" unless system("bw sync > /dev/null 2>&1") Try / catch
attempts = 0
begin
adapter.fetch(names, account: account)
rescue RuntimeError => e
attempts += 1
if e.message.include?("Failed to sync Bitwarden") && attempts < 3
sleep 2 ** attempts
retry
end
raise
end Prevention
- Treat sync failures as transient: retry with backoff before failing the deploy
- Run `bw sync` as a pre-flight in CI to distinguish vault-outage from item problems
When it happens
Trigger: Transient network failure reaching Bitwarden's API; an invalidated session (BW_SESSION rejected server-side mid-run); Bitwarden service outage or self-hosted server down; clock skew breaking TLS.
Common situations: Flaky connectivity on CI runners; long-running agents whose session expires between unlock and sync; self-hosted Bitwarden (Vaultwarden) instances restarting during deploy.
Related errors
- Failed to login to and unlock Bitwarden
- Could not read #{item} from Bitwarden
- Item #{item} is not a login type item and no fields were spe
- Could not find field #{field} in item #{item} in Bitwarden
- Bitwarden CLI is not installed
AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21).
Data as JSON: /api/errors/8f3d96d8f208d3e5.
Report an issue: GitHub.