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)
          else

View on GitHub (pinned to eee0083b38)

Solutions

  1. Retry the kamal secrets fetch — sync failures are often transient
  2. Run bw sync manually to see the underlying error; if it reports an invalid session, bw unlock again and re-export BW_SESSION
  3. Check connectivity/health of api.bitwarden.com (or your self-hosted server) and system clock accuracy
  4. 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

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


AI-assisted analysis of basecamp/kamal@eee0083b38 (2026-08-21). Data as JSON: /api/errors/8f3d96d8f208d3e5. Report an issue: GitHub.