{"record":{"id":"7bb6c3fc1d611e93","repo":"puppetlabs/puppet","slug":"the-code-loaded-from-source-ref-does-not-define-7bb6c3","errorCode":null,"errorMessage":"The code loaded from %{source_ref} does not define the plan '%{plan_name}' - it is empty.","messagePattern":"The code loaded from %(.+?) does not define the plan '%(.+?)' - it is empty\\.","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/pops/loader/puppet_plan_instantiator.rb","lineNumber":36,"sourceCode":"  #\n  def self.create(loader, typed_name, source_ref, pp_code_string)\n    parser = Parser::EvaluatingParser.new()\n\n    # parse and validate\n    result = parser.parse_string(pp_code_string, source_ref)\n\n    # The parser attaches all definitions, including those nested in apply\n    # blocks, to the Program object. Node definitions in apply blocks are\n    # perfectly legal and don't count as the file containing multiple\n    # definitions for this purpose. By this point, we've already validated that\n    # there are no node definitions *outside* apply blocks, so we simply ignore\n    # them here.\n    definitions = result.definitions.reject { |definition| definition.is_a?(Puppet::Pops::Model::NodeDefinition) }\n\n    # Only one plan is allowed (and no other definitions)\n    case definitions.size\n    when 0\n      raise ArgumentError, _(\"The code loaded from %{source_ref} does not define the plan '%{plan_name}' - it is empty.\") % { source_ref: source_ref, plan_name: typed_name.name }\n    when 1\n      # ok\n    else\n      raise ArgumentError, _(\"The code loaded from %{source_ref} must contain only the plan '%{plan_name}' - it has additional definitions.\") % { source_ref: source_ref, plan_name: typed_name.name }\n    end\n    the_plan_definition = definitions[0]\n\n    unless the_plan_definition.is_a?(Model::PlanDefinition)\n      raise ArgumentError, _(\"The code loaded from %{source_ref} does not define the plan '%{plan_name}' - no plan found.\") % { source_ref: source_ref, plan_name: typed_name.name }\n    end\n\n    unless the_plan_definition.name == typed_name.name\n      expected = typed_name.name\n      actual = the_plan_definition.name\n      raise ArgumentError, _(\"The code loaded from %{source_ref} produced plan with the wrong name, expected %{expected}, actual %{actual}\") % { source_ref: source_ref, expected: expected, actual: actual }\n    end\n    unless result.body == the_plan_definition\n      raise ArgumentError, _(\"The code loaded from %{source} contains additional logic - can only contain the plan %{plan_name}\") % { source: source_ref, plan_name: typed_name.name }","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/pops/loader/puppet_plan_instantiator.rb#L18-L54","documentation":"Raised by PuppetPlanInstantiator when the plan file being loaded contains zero definitions after NodeDefinitions are rejected. A plan file must contain exactly one plan statement; an empty file, a comments-only file, or a file holding only node definitions fails with this ArgumentError at load time. Bolt and the Puppet orchestrator hit this when resolving a plan by name.","triggerScenarios":"Loading modules/<mod>/plans/<name>.pp (or a Bolt plan) where the parsed Program's definitions, after `.reject { |d| d.is_a?(Model::NodeDefinition) }`, is empty -- `definitions.size == 0` in the case statement.","commonSituations":"Placeholder plan created with `touch plan.pp`; plan statement commented out; copy/paste leaving only comments; CI or scaffolding generating empty stubs; Git merge conflict resolved to an empty body.","solutions":["Add a plan statement matching the file path: `plan <module>::<plan_name> { ... }`","Delete the .pp file from plans/ if the plan is not intended to exist","Verify via the message's source_ref that the loader is reading the file you expect"],"exampleFix":"# before - modules/mymod/plans/deploy.pp\n# TODO: write this plan\n\n# after\nplan mymod::deploy(\n  TargetSpec $nodes\n) {\n  run_task('mymod::install', $nodes)\n}","handlingStrategy":"validation","validationCode":"model = Puppet::Pops::Parser::Parser.new.parse_string(plan_code, source_ref)\ndefs = model.definitions.reject { |d| d.is_a?(Puppet::Pops::Model::NodeDefinition) }\nraise ArgumentError, \"#{source_ref} defines no plan\" if defs.empty?","typeGuard":null,"tryCatchPattern":"begin\n  plan = loader.load_typed(typed_name)\nrescue ArgumentError => e\n  raise unless e.message.include?('it is empty')\n  warn \"empty plan file: #{e.message}\"\nend","preventionTips":["Never ship empty .pp files in plans/","Run bolt plan show after adding or editing plans to confirm they all load","Treat plan stubs as errors in CI, not warnings"],"tags":["puppet","bolt","plan","loader","module"],"backgroundTag":"puppet-plan-file-empty","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}