basecamp/kamal · error · RuntimeError

#{key} is present more than once

Error message

#{key} is present more than once

What it means

Raised in Enpass#parse_result_and_take_secrets while folding the JSON returned by `enpass-cli -json -vault ... show ...`. Each item yields key = [title, label].compact.join('/'); when the same key appears a second time AND that key matches one of the requested secrets, kamal refuses to guess which password to use and raises. It is a duplicate-detection guard on top of an item/title/label tuple, not a general vault corruption error.

Source

Thrown at lib/kamal/secrets/adapters/enpass.rb:63

        else
          secret_titles << key
        end
      end.to_a
    end

    def parse_result_and_take_secrets(unparsed_result, secrets)
      result = JSON.parse(unparsed_result)

      result.reduce({}) do |secrets_with_passwords, item|
        title = item["title"]
        label = item["label"]
        password = item["password"]

        if title && password.present?
          key = [ title, label ].compact.reject(&:empty?).join("/")

          if secrets.include?(title) || secrets.include?(key)
            raise RuntimeError, "#{key} is present more than once" if secrets_with_passwords[key]
            secrets_with_passwords[key] = password
          end
        end

        secrets_with_passwords
      end
    end
end

View on GitHub (pinned to eee0083b38)

Solutions

  1. Open Enpass and locate the duplicated title (search by the key shown in the error, e.g. 'API_KEY'); delete or rename the stale copy so the title is unique.
  2. If both entries are legitimate, rename one (e.g. API_KEY_OLD) and keep the requested name unique in the vault.
  3. If the duplicate is title-vs-label (two fields with the same label in one item), edit the item in Enpass so each labeled field is distinct, then re-run `kamal secrets pull`.

Example fix

# before: vault has two items titled DB_PASSWORD
#   kamal secrets pull  # -> "DB_PASSWORD is present more than once"

# after: rename one item in the Enpass app
#   DB_PASSWORD        (keep, unique)
#   DB_PASSWORD_backup (renamed)
kamal secrets pull
Defensive patterns

Strategy: validation

Validate before calling

def unique_secret_names?(names)
  names.tally.values.all? { |count| count == 1 }
end

# Also validate the vault side before kamal does:
require "open3"
out, _err, st = Open3.capture3("enpass-cli", "-json", "-vault", "Primary", "show", *names)
if st.success?
  keys = JSON.parse(out).map { |i| [i["title"], i["label"]].compact.reject(&:empty?).join("/") }
  abort "Duplicate items in vault: #{keys.group_by(&:itself).select { |_, v| v.size > 1 }.keys.join(', ')}" unless keys.size == keys.uniq.size
end

Try / catch

begin
  adapter.fetch(names, from: "Primary", account: nil)
rescue RuntimeError => e
  if e.message.end_with?("is present more than once")
    raise "Duplicate Enpass item #{e.message}: rename the stale copy in the vault, then re-pull"
  end
  raise
end

Prevention

When it happens

Trigger: adapter.fetch([...], from:, account:) where the vault contains two Enpass items with the same title and same (or empty) label, both with passwords, and that title (or title/label key) is in your requested secrets list; also one item exposing multiple fields with the identical label under the same title.

Common situations: Years-old vaults with 'Test', 'Login', or service-name duplicates created by sync/import; renaming an item but leaving a copy; items where the password lives under different fields and label collides after compact/reject of empty strings.

Related errors


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