{"record":{"id":"e2db86da2af72495","repo":"puppetlabs/puppet","slug":"somehow-looped-more-than-1000-times-while-evaluati","errorCode":null,"errorMessage":"Somehow looped more than 1000 times while evaluating host catalog","messagePattern":"Somehow looped more than 1000 times while evaluating host catalog","errorType":"exception","errorClass":"Puppet::ParseError","httpStatus":null,"severity":"error","filePath":"lib/puppet/parser/compiler.rb","lineNumber":383,"sourceCode":"  # and defined resources can generate new resources, which themselves could\n  # be defined resources.\n  def evaluate_generators\n    count = 0\n    loop do\n      done = true\n\n      Puppet::Util::Profiler.profile(_(\"Iterated (%{count}) on generators\") % { count: count + 1 }, [:compiler, :iterate_on_generators]) do\n        # Call collections first, then definitions.\n        done = false if evaluate_collections\n        done = false if evaluate_definitions\n      end\n\n      break if done\n\n      count += 1\n\n      if count > 1000\n        raise Puppet::ParseError, _(\"Somehow looped more than 1000 times while evaluating host catalog\")\n      end\n    end\n  end\n  protected :evaluate_generators\n\n  # Find and evaluate our main object, if possible.\n  def evaluate_main\n    krt = environment.known_resource_types\n    @main = krt.find_hostclass('') || krt.add(Puppet::Resource::Type.new(:hostclass, ''))\n    @topscope.source = @main\n    @main_resource = Puppet::Parser::Resource.new('class', :main, :scope => @topscope, :source => @main)\n    @topscope.resource = @main_resource\n\n    add_resource(@topscope, @main_resource)\n\n    @main_resource.evaluate\n  end\n","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/parser/compiler.rb#L365-L401","documentation":"Catalog evaluation iterates over collections and definitions in passes until a pass adds no new work (done stays true). A counter caps the loop at 1000 passes; exceeding it raises Puppet::ParseError, guarding against non-converging catalog generation - effectively an infinite loop during compile, usually a definition that keeps instantiating more resources forever.","triggerScenarios":"A defined type that (directly or through a cycle of other defines) instantiates itself with ever-new titles; virtual resource collections that generate additional virtual resources each pass; definitions whose evaluation re-triggers the same generator.","commonSituations":"Recursive defines (define a { a { \"${title}-x\": } }); module upgrades introducing cyclic generation; collectors that expand into new collector-matching resources each round.","solutions":["Reproduce with puppet apply --noop --trace and enable debug logging to see which generator keeps iterating ('Iterated (N) on generators' profile messages)","Inspect recently changed defines for self-instantiation and break the cycle (fixed set of titles, restructure as classes)","Replace recursion with iteration (each blocks) inside a single define"],"exampleFix":"// before\ndefine app::chain($n) { app::chain { \"${n}-a\": n => \"${n}-a\" } }\n\n// after\ndefine app::chain($parts) { $parts.each |$p| { file { \"${title}-${p}\": ensure => present } } }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  compiler.compile\nrescue Puppet::ParseError => e\n  raise NonConvergingCatalog, e.message if e.message.include?('looped more than 1000 times')\n  raise\nend","preventionTips":["Never instantiate a define from inside itself with generated titles","Watch 'Iterated (N) on generators' profiler messages in debug output during development","Smoke-test changed defines with puppet apply --noop --evaltrace before rollout"],"tags":["puppet","compiler","recursion","infinite-loop","dsl"],"backgroundTag":"infinite-loop-guard","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}