basecamp/kamal · error · RuntimeError

Could not read #{secrets} from LastPass

Error message

Could not read #{secrets} from LastPass

What it means

Raised in LastPass#fetch_secrets when the backtick `lpass show <names> --json` exits non-zero. This happens after a successful login/status check and covers CLI-level read failures: unknown secret names (lpass errors on any unfound name), ambiguous matches, vault sync problems, or connectivity failure to LastPass servers. Distinguish it from error 73, which fires when the command succeeds but requested names are absent from the JSON.

Source

Thrown at lib/kamal/secrets/adapters/last_pass.rb:17

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"]
        end

        if (missing_items = secrets - results.keys).any?
          raise RuntimeError, "Could not find #{missing_items.join(", ")} in LastPass"
        end
      end
    end

    def check_dependencies!
      raise RuntimeError, "LastPass CLI is not installed" unless cli_installed?
    end

View on GitHub (pinned to eee0083b38)

Solutions

  1. Test exactly: `lpass show '<NAME>' --json` for each requested name and fix/remove failures (match by name or ID from `lpass ls`).
  2. Run `lpass sync` to refresh the local vault, then retry `kamal secrets pull`.
  3. For shared-folder items, reference the name exactly as `lpass ls` prints it (folder/name) in your secrets list.
  4. If names are ambiguous (multiple entries share a name), use the numeric ID from `lpass ls --format "%au %an"`.

Example fix

# before: deploy.yml lists a name lpass cannot resolve
#   secrets: [ "RAILS_MASTER_KEY" ]  # not in vault -> lpass exits 1

# after: create it or reference the real entry
#   lpass add --notes RAILS_MASTER_KEY  (paste key)
kamal secrets pull
Defensive patterns

Strategy: try-catch

Validate before calling

require "open3"

def lpass_readable?(name)
  _out, _err, status = Open3.capture3("lpass", "show", name, "--json")
  status.success?
end

bad = names.reject { |n| lpass_readable?(n) }
abort "lpass cannot read: #{bad.join(', ')} — sync or fix names" if bad.any?

Try / catch

begin
  secrets = adapter.fetch(names, account: ACCOUNT)
rescue RuntimeError => e
  if e.message.include?("Could not read") && e.message.include?("from LastPass")
    raise "lpass show failed: run `lpass sync` and verify each name with `lpass show <name> --json`"
  end
  raise
end

Prevention

When it happens

Trigger: adapter.fetch([...], account:) when any requested name (after joining with the from prefix) is not resolvable by `lpass show` — lpass treats a non-existent name as a hard error (non-zero exit); also when the local vault is out of sync or the lpass session silently expired mid-run.

Common situations: Secret names in deploy.yml not matching LastPass entry unique-IDs/names; entries in a shared folder referenced without the folder path; running right after password DB changes before `lpass sync`; flaky network causing lpass to fail mid-fetch.

Related errors


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