{"record":{"id":"86bf66687998facc","repo":"puppetlabs/puppet","slug":"the-code-loaded-from-source-ref-must-contain-on-86bf66","errorCode":null,"errorMessage":"The code loaded from %{source_ref} must contain only the plan '%{plan_name}' - it has additional definitions.","messagePattern":"The code loaded from %(.+?) must contain only the plan '%(.+?)' - it has additional definitions\\.","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/puppet/pops/loader/puppet_plan_instantiator.rb","lineNumber":40,"sourceCode":"    # 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 }\n    end\n\n    # Adapt the function definition with loader - this is used from logic contained in it body to find the\n    # loader to use when making calls to the new function API. Such logic have a hard time finding the closure (where","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/pops/loader/puppet_plan_instantiator.rb#L22-L58","documentation":"Raised by PuppetPlanInstantiator when the plan file contains more than one definition. Only one plan definition (and no other definitions) may live in a plan file; two plans, or a plan plus a class/function definition, hit the `else` branch of the size case and raise this ArgumentError during load.","triggerScenarios":"`definitions.size > 1` after rejecting NodeDefinitions -- e.g. plans/deploy.pp contains both `plan mymod::deploy(...)` and `plan mymod::rollback(...)`, or a plan plus a class definition.","commonSituations":"Copying a plan inside the same file instead of a new file; moving class code into a plan file during a Bolt migration; combining small plans into one file for convenience.","solutions":["Split the definitions into separate files under plans/, one plan per file named after the plan","Delete definitions that do not belong in the plan file","Move non-plan definitions to their conventional locations (classes to manifests/, functions to functions/)"],"exampleFix":"# before - modules/mymod/plans/deploy.pp (two plans in one file)\nplan mymod::deploy($n) { run_command('uptime', $n) }\nplan mymod::rollback($n) { run_command('rollback', $n) }\n\n# after - plans/deploy.pp\nplan mymod::deploy($n) { run_command('uptime', $n) }\n# and plans/rollback.pp\nplan mymod::rollback($n) { run_command('rollback', $n) }","handlingStrategy":"validation","validationCode":"defs = model.definitions.reject { |d| d.is_a?(Puppet::Pops::Model::NodeDefinition) }\nraise ArgumentError, \"#{source_ref} has #{defs.size} definitions; expected 1\" unless defs.size == 1","typeGuard":null,"tryCatchPattern":"begin\n  loader.load_typed(typed_name)\nrescue ArgumentError => e\n  raise unless e.message.include?('additional definitions')\n  warn \"splitting needed: #{e.message}\"\nend","preventionTips":["Adopt one-plan-per-file as a lint rule in code review","After copying a plan, immediately move the copy to its own file","Use puppet parser validate on the module to catch multi-definition files"],"tags":["puppet","bolt","plan","loader","module-structure"],"backgroundTag":"puppet-plan-multiple-definitions","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}