puppetlabs/puppet · error · ArgumentError
The code loaded from %{source_ref} must contain only the pla
Error message
The code loaded from %{source_ref} must contain only the plan '%{plan_name}' - it has additional definitions. What it means
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.
Source
Thrown at lib/puppet/pops/loader/puppet_plan_instantiator.rb:40
# parse and validate
result = parser.parse_string(pp_code_string, source_ref)
# The parser attaches all definitions, including those nested in apply
# blocks, to the Program object. Node definitions in apply blocks are
# perfectly legal and don't count as the file containing multiple
# definitions for this purpose. By this point, we've already validated that
# there are no node definitions *outside* apply blocks, so we simply ignore
# them here.
definitions = result.definitions.reject { |definition| definition.is_a?(Puppet::Pops::Model::NodeDefinition) }
# Only one plan is allowed (and no other definitions)
case definitions.size
when 0
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 }
when 1
# ok
else
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 }
end
the_plan_definition = definitions[0]
unless the_plan_definition.is_a?(Model::PlanDefinition)
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 }
end
unless the_plan_definition.name == typed_name.name
expected = typed_name.name
actual = the_plan_definition.name
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 }
end
unless result.body == the_plan_definition
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 }
end
# Adapt the function definition with loader - this is used from logic contained in it body to find the
# loader to use when making calls to the new function API. Such logic have a hard time finding the closure (whereView on GitHub (pinned to e227c27540)
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/)
Example fix
# before - modules/mymod/plans/deploy.pp (two plans in one file)
plan mymod::deploy($n) { run_command('uptime', $n) }
plan mymod::rollback($n) { run_command('rollback', $n) }
# after - plans/deploy.pp
plan mymod::deploy($n) { run_command('uptime', $n) }
# and plans/rollback.pp
plan mymod::rollback($n) { run_command('rollback', $n) } Defensive patterns
Strategy: validation
Validate before calling
defs = model.definitions.reject { |d| d.is_a?(Puppet::Pops::Model::NodeDefinition) }
raise ArgumentError, "#{source_ref} has #{defs.size} definitions; expected 1" unless defs.size == 1 Try / catch
begin
loader.load_typed(typed_name)
rescue ArgumentError => e
raise unless e.message.include?('additional definitions')
warn "splitting needed: #{e.message}"
end Prevention
- 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
When it happens
Trigger: `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.
Common situations: 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.
Related errors
- The code loaded from %{source_ref} does not define the plan
- The code loaded from %{source_ref} does not define the plan
- The code loaded from %{source_ref} produced plan with the wr
- The code loaded from %{source} contains additional logic - c
- Module %{module_name} not found in environment %{environment
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/86bf66687998facc.
Report an issue: GitHub.