puppetlabs/puppet · error · ArgumentError

%{option} already has an after_action set

Error message

%{option} already has an after_action set

What it means

OptionBuilder#after_action permits a single hook per option; a second registration raises because `@option.after_action` is already set (lib/puppet/interface/option_builder.rb:72). Like its before_action twin, the DSL offers no chaining, so duplicates are rejected outright at definition time.

Source

Thrown at lib/puppet/interface/option_builder.rb:72

      # TRANSLATORS 'before_action' is a method name and should not be translated
      raise ArgumentError, _("before_action takes three arguments, action, args, and options")
    end

    @option.before_action = block
  end

  # Sets a block to be executed after an action is invoked.
  # !(see before_action)
  # @api public
  # @dsl Faces
  def after_action(&block)
    unless block
      # TRANSLATORS 'after_action' is a method name and should not be translated
      raise ArgumentError, _("%{option} after_action requires a block") % { option: @option }
    end
    if @option.after_action
      # TRANSLATORS 'after_action' is a method name and should not be translated
      raise ArgumentError, _("%{option} already has an after_action set") % { option: @option }
    end
    unless block.arity == 3 then
      # TRANSLATORS 'after_action' is a method name and should not be translated
      raise ArgumentError, _("after_action takes three arguments, action, args, and options")
    end

    @option.after_action = block
  end

  # Sets whether the option is required. If no argument is given it
  # defaults to setting it as a required option.
  # @api public
  # @dsl Faces
  def required(value = true)
    @option.required = value
  end

  # Sets a block that will be used to compute the default value for this

View on GitHub (pinned to e227c27540)

Solutions

  1. Combine both bodies into one `after_action` block
  2. Remove the leftover hook after refactoring
  3. Split distinct concerns across before_action and after_action where timing allows

Example fix

# before
option "--foo" do
  after_action { |a, args, opts| log(opts) }
  after_action { |a, args, opts| cleanup(opts) }
end

# after
option "--foo" do
  after_action do |a, args, opts|
    log(opts)
    cleanup(opts)
  end
end
Defensive patterns

Strategy: validation

Validate before calling

# before registering on an Option object
raise ArgumentError, 'after_action already set' if opt.after_action
opt.after_action = proc { |action, args, options| cleanup(options) }

Prevention

When it happens

Trigger: Two `after_action` blocks in one option block; a shared helper called twice that registers the hook each time.

Common situations: Adding logging and cleanup separately instead of together; merging code where both halves kept their own after_action.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/d1067f0b36acce60. Report an issue: GitHub.