basecamp/kamal · error · Kamal::ConfigurationError

Invalid hooks_output '#{level}'#{context}, must be one of: #

Error message

Invalid hooks_output '#{level}'#{context}, must be one of: #{HOOKS_OUTPUT_LEVELS.join(', ')}

What it means

Kamal::Configuration#ensure_valid_hooks_output! validates the `hooks_output:` setting — either a single Symbol/String level, or a Hash mapping individual hooks to levels — against HOOKS_OUTPUT_LEVELS, which is exactly [:quiet, :verbose]. Any other value (e.g. `detailed`, `debug`, `info`) raises with the invalid level, the hook context when the failure is hash-keyed, and the allowed list.

Source

Thrown at lib/kamal/configuration.rb:447

    end

    def role_names
      raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
    end

    def ensure_valid_hooks_output!
      case raw_config.hooks_output
      when Symbol, String
        validate_hooks_output_level!(raw_config.hooks_output.to_sym)
      when Hash
        raw_config.hooks_output.each { |hook, level| validate_hooks_output_level!(level.to_sym, hook) }
      end
    end

    def validate_hooks_output_level!(level, hook = nil)
      return if HOOKS_OUTPUT_LEVELS.include?(level)
      context = hook ? " for hook '#{hook}'" : ""
      raise Kamal::ConfigurationError, "Invalid hooks_output '#{level}'#{context}, must be one of: #{HOOKS_OUTPUT_LEVELS.join(', ')}"
    end

    def git_version
      @git_version ||=
        if Kamal::Git.used?
          if Kamal::Git.uncommitted_changes.present? && !builder.git_clone?
            uncommitted_suffix = "_uncommitted_#{SecureRandom.hex(8)}"
          end
          [ Kamal::Git.revision, uncommitted_suffix ].compact.join
        else
          raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
        end
    end
end

View on GitHub (pinned to eee0083b38)

Solutions

  1. Use one of the two valid levels: `hooks_output: quiet` or `hooks_output: verbose`.
  2. For per-hook control, use the hash form with valid values: `hooks_output: { pre-deploy: verbose }`.
  3. If the value comes from ENV, restrict it in the template: `<%= %w[quiet verbose].include?(ENV["HOOKS_OUTPUT"]) ? ENV["HOOKS_OUTPUT"] : "quiet" %>`.

Example fix

# config/deploy.yml — before
hooks_output: detailed

# after
hooks_output: verbose
Defensive patterns

Strategy: validation

Validate before calling

LEVELS = %i[quiet verbose].freeze

def valid_hooks_output?(value)
  case value
  when String, Symbol then LEVELS.include?(value.to_sym)
  when Hash then value.values.all? { |v| LEVELS.include?(v.to_sym) }
  else true # nil is fine (default)
  end
end

Type guard

def kamal_hooks_output?(value)
  return true if value.nil?
  case value
  when String, Symbol then %w[quiet verbose].include?(value.to_s)
  when Hash then value.values.all? { |v| %w[quiet verbose].include?(v.to_s) }
  else false
  end
end

Try / catch

begin
  config = Kamal::Configuration.new(create_config_files: false)
rescue Kamal::ConfigurationError => e
  puts "Deploy config invalid: #{e.message}"
  exit 1
end

Prevention

When it happens

Trigger: Setting `hooks_output: detailed` (or any word other than quiet/verbose) in config/deploy.yml; a Hash form like `hooks_output: { "pre-deploy": "info" }`; values sourced from ENV strings that were never whitelisted; configs written against docs from a different kamal version whose level names changed.

Common situations: Guessing verbosity level names instead of checking constants; copying CI log-level conventions (info/debug) into kamal config; per-hook overrides added without validating each value.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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