basecamp/kamal · error · RuntimeError

#{error['SecretId']}: #{error['Message']}

Error message

#{error['SecretId']}: #{error['Message']}

What it means

batch-get-secret-value can return HTTP 200 while still reporting per-secret failures in its Errors array; this RuntimeError surfaces each one as "<SecretId>: <Message>" joined by spaces. The aws CLI exited successfully but AWS refused or could not find at least one requested secret.

Source

Thrown at lib/kamal/secrets/adapters/aws_secrets_manager.rb:43

          results["#{secret_name}"] = secret["SecretString"]
        end
      end
    end

    def get_from_secrets_manager(secrets, account: nil)
      args = [ "aws", "secretsmanager", "batch-get-secret-value", "--secret-id-list" ] + secrets.map(&:shellescape)
      args += [ "--profile", account.shellescape ] if account
      args += [ "--output", "json" ]
      cmd = args.join(" ")

      `#{cmd}`.tap do |secrets|
        raise RuntimeError, "Could not read #{secrets} from AWS Secrets Manager" unless $?.success?

        secrets = JSON.parse(secrets)

        return secrets["SecretValues"] unless secrets["Errors"].present?

        raise RuntimeError, secrets["Errors"].map { |error| "#{error['SecretId']}: #{error['Message']}" }.join(" ")
      end
    end

    def stringify_secret_value(value)
      value.is_a?(String) ? value : JSON.dump(value)
    end

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

    def cli_installed?
      `aws --version 2> /dev/null`
      $?.success?
    end
end

View on GitHub (pinned to eee0083b38)

Solutions

  1. Verify the secret exists in the right account/region: aws secretsmanager describe-secret --secret-id <SecretId from the message> --profile <account>
  2. Add the missing IAM permission (secretsmanager:GetSecretValue, secretsmanager:DescribeSecret) for the secret's ARN to the principal in use
  3. Fix the secret name in the kamal secrets fetch arguments to match the full name in AWS
  4. Point at the correct region/profile (export AWS_REGION=... or pass --account matching an aws profile configured for that account)

Example fix

# error: myapp/RAILS_MASTER_KEY: User is not authorized... 

# fix (IAM policy for the CI user/role)
{
  "Effect": "Allow",
  "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
  "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:myapp/*"
}

# then retry
kamal secrets fetch -a aws_secrets_manager --account prod RAILS_MASTER_KEY
Defensive patterns

Strategy: try-catch

Validate before calling

# Pre-check each secret exists and is readable before the batch fetch
secret_names.each do |name|
  ok = system("aws secretsmanager describe-secret --secret-id #{name.shellescape} #{account ? "--profile #{account.shellescape}" : ""} > /dev/null 2>&1")
  warn "warning: #{name} not visible to this credentials/region" unless ok
end

Try / catch

begin
  secrets = adapter.fetch(secret_names, account: account)
rescue RuntimeError => e
  # e.message is "SecretId: AWS message" pairs — surface them per-secret
  e.message.split(/ (?=\S+: )/).each { |pair| warn "AWS secretsmanager: #{pair}" }
  raise
end

Prevention

When it happens

Trigger: ResourceNotFoundException for a secret id that does not exist in the configured account+region; AccessDeniedException when the IAM principal lacks secretsmanager:GetSecretValue/DescribeSecret; InvalidRequestException for malformed ids; secret exists but only in another region or another profile's account.

Common situations: Secret name typo or missing environment prefix (e.g. myapp/staging vs myapp/production); IAM policy scoped to specific secret ARNs that omit the requested one; default region pointing at us-east-1 while secrets live in eu-west-1; using the wrong --account profile.

Related errors


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