{"record":{"id":"d1067f0b36acce60","repo":"puppetlabs/puppet","slug":"option-already-has-an-after-action-set","errorCode":null,"errorMessage":"%{option} already has an after_action set","messagePattern":"%(.+?) already has an after_action set","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/interface/option_builder.rb","lineNumber":72,"sourceCode":"      # TRANSLATORS 'before_action' is a method name and should not be translated\n      raise ArgumentError, _(\"before_action takes three arguments, action, args, and options\")\n    end\n\n    @option.before_action = block\n  end\n\n  # Sets a block to be executed after an action is invoked.\n  # !(see before_action)\n  # @api public\n  # @dsl Faces\n  def after_action(&block)\n    unless block\n      # TRANSLATORS 'after_action' is a method name and should not be translated\n      raise ArgumentError, _(\"%{option} after_action requires a block\") % { option: @option }\n    end\n    if @option.after_action\n      # TRANSLATORS 'after_action' is a method name and should not be translated\n      raise ArgumentError, _(\"%{option} already has an after_action set\") % { option: @option }\n    end\n    unless block.arity == 3 then\n      # TRANSLATORS 'after_action' is a method name and should not be translated\n      raise ArgumentError, _(\"after_action takes three arguments, action, args, and options\")\n    end\n\n    @option.after_action = block\n  end\n\n  # Sets whether the option is required. If no argument is given it\n  # defaults to setting it as a required option.\n  # @api public\n  # @dsl Faces\n  def required(value = true)\n    @option.required = value\n  end\n\n  # Sets a block that will be used to compute the default value for this","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/interface/option_builder.rb#L54-L90","documentation":"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.","triggerScenarios":"Two `after_action` blocks in one option block; a shared helper called twice that registers the hook each time.","commonSituations":"Adding logging and cleanup separately instead of together; merging code where both halves kept their own after_action.","solutions":["Combine both bodies into one `after_action` block","Remove the leftover hook after refactoring","Split distinct concerns across before_action and after_action where timing allows"],"exampleFix":"# before\noption \"--foo\" do\n  after_action { |a, args, opts| log(opts) }\n  after_action { |a, args, opts| cleanup(opts) }\nend\n\n# after\noption \"--foo\" do\n  after_action do |a, args, opts|\n    log(opts)\n    cleanup(opts)\n  end\nend","handlingStrategy":"validation","validationCode":"# before registering on an Option object\nraise ArgumentError, 'after_action already set' if opt.after_action\nopt.after_action = proc { |action, args, options| cleanup(options) }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["One after_action per option; fold extra work into the single block","Review copy-pasted option blocks for duplicated hooks","When concerns differ in timing, split across before_action/after_action rather than stacking"],"tags":["puppet","faces","dsl","duplicate","argumenterror"],"backgroundTag":"duplicate-declaration","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}