basecamp/kamal · error · RuntimeError

Could not read #{secrets} from Passbolt

Error message

Could not read #{secrets} from Passbolt

What it means

Kamal's Passbolt secrets adapter reads secrets by shelling out to the passbolt CLI: `passbolt list resources <filter> <folders> --column name --column password --json`. This RuntimeError (passbolt.rb:51) is raised when that process exits non-zero ($?.success? is false), meaning the CLI itself failed before any secret name could be matched. It points to a CLI/session/server problem, not a wrong secret name (that produces the separate 'Could not find the following secrets' error).

Source

Thrown at lib/kamal/secrets/adapters/passbolt.rb:51

          folder_path.each do |folder_name|
            current_path << folder_name
            matching_folders = folders.select { |f| get_folder_path(f, folders) == current_path.join("/") }
            current_folder = matching_folders.first if matching_folders.any?
          end

          if current_folder
            filter_conditions << "(Name == #{secret_name.shellescape.inspect} && FolderParentID == #{current_folder["id"].shellescape.inspect})"
          end
        else
          # for root level secrets (no folders)
          filter_conditions << "Name == #{secret_name.shellescape.inspect}"
        end
      end

      filter_condition = filter_conditions.any? ? "--filter '#{filter_conditions.join(" || ")}'" : ""
      items = `passbolt list resources #{filter_condition} #{folders.map { |item| "--folder #{item["id"].to_s.shellescape}" }.join(" ")} --column name --column password --json`
      raise RuntimeError, "Could not read #{secrets} from Passbolt" unless $?.success?
      items = JSON.parse(items)
      found_names = items.map { |item| item["name"] }
      missing_secrets = secret_names - found_names
      raise RuntimeError, "Could not find the following secrets in Passbolt: #{missing_secrets.join(", ")}" if missing_secrets.any?

      items.to_h { |item| [ item["name"], item["password"] ] }
    end

    def secrets_get_folders(secrets)
      # extract all folder paths (both parent and nested)
      folder_paths = secrets
        .select { |s| s.include?("/") }
        .map { |s| s.split("/")[0..-2] } # get all parts except the secret name
        .uniq

      return [] if folder_paths.empty?

      all_folders = []

View on GitHub (pinned to eee0083b38)

Solutions

  1. Run the exact command manually to surface the real error: `passbolt list resources --column name --column password --json`, and inspect stderr and the exit code.
  2. Re-run `passbolt configure` (server URL, private key path, key fingerprint), re-verify the server key, and confirm authentication succeeds.
  3. Verify network access from the machine running Kamal to the Passbolt API host, and that the API is up.
  4. Confirm the CLI config lives in the HOME of the same user/process that runs Kamal (CI shells and systemd units often differ).
  5. Upgrade or pin a passbolt CLI version whose `list resources` subcommand supports the filter/folder flags used by the adapter.

Example fix

// before
# deploy.yml
secrets:
  - DB_PASSWORD=passbolt/infra/pg
# kamal deploy -> RuntimeError: Could to read [...] from Passbolt (opaque)

// after
# surface the CLI's real error first, fix auth/config, then re-run
$ passbolt list resources --column name --column password --json
echo "exit=$?"
Defensive patterns

Strategy: try-catch

Validate before calling

# Preflight the CLI before any Kamal run that resolves passbolt/* secrets
`passbolt list resources --json 2>/dev/null`
abort('Passbolt CLI read failed — check auth/config') unless $?.success?

Try / catch

begin
  # any Kamal task that resolves passbolt/* secrets (CLI or Kamal::Command)
rescue RuntimeError => e
  if e.message =~ /Could not read .* from Passbolt/
    abort "#{e.message} — diagnose with: passbolt list resources --json"
  else
    raise
  end
end

Prevention

When it happens

Trigger: Any Kamal run that resolves passbolt secret references (e.g. `kamal secrets fetch`, or `kamal deploy` with `secrets: - FOO=passbolt/folder/NAME` in deploy.yml) while the backtick command fails: the CLI is not authenticated or its session expired, the Passbolt server is unreachable, the server URL in CLI config is wrong, the GPG private key is missing/invalid, or the installed CLI version rejects the flags used.

Common situations: `passbolt configure` was never run or points at the wrong server; the deploy box lost its GPG key or the server key rotated; CI runners lacking the CLI config under HOME; a passbolt CLI version whose `list resources` flags differ; Passbolt API down or blocked by a firewall.

Related errors


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