puppetlabs/puppet · error · ArgumentError
The code loaded from %{source_ref} does not define the plan
Error message
The code loaded from %{source_ref} does not define the plan '%{plan_name}' - it is empty. What it means
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.
Source
Thrown at lib/puppet/pops/loader/puppet_plan_instantiator.rb:36
#
def self.create(loader, typed_name, source_ref, pp_code_string)
parser = Parser::EvaluatingParser.new()
# 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 }View on GitHub (pinned to e227c27540)
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
Example fix
# before - modules/mymod/plans/deploy.pp
# TODO: write this plan
# after
plan mymod::deploy(
TargetSpec $nodes
) {
run_task('mymod::install', $nodes)
} Defensive patterns
Strategy: validation
Validate before calling
model = Puppet::Pops::Parser::Parser.new.parse_string(plan_code, source_ref)
defs = model.definitions.reject { |d| d.is_a?(Puppet::Pops::Model::NodeDefinition) }
raise ArgumentError, "#{source_ref} defines no plan" if defs.empty? Try / catch
begin
plan = loader.load_typed(typed_name)
rescue ArgumentError => e
raise unless e.message.include?('it is empty')
warn "empty plan file: #{e.message}"
end Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- The code loaded from %{source_ref} must contain only the pla
- 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
- The code loaded from %{source} contains additional logic - c
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/7bb6c3fc1d611e93.
Report an issue: GitHub.