puppetlabs/puppet · error · Puppet::Error

One or more resource dependency cycles detected in graph

Error message

One or more resource dependency cycles detected in graph

What it means

When the transaction traverses the relationship graph, Puppet::Graph::Ranks traversal detects cycles among require/before/notify/subscribe (and containment) edges. The graph_cycle_handler marks every involved resource's status as failed ('resource is part of a dependency cycle') so the report is accurate, then raises Puppet::Error 'One or more resource dependency cycles detected in graph' and the run stops without syncing anything.

Source

Thrown at lib/puppet/transaction.rb:170

        Puppet.log_exception(detail, _("post_resource_eval failed for provider %{provider}") % { provider: provider })
      end

      persistence.save if persistence.enabled?(catalog)
    end

    # Graph cycles are returned as an array of arrays
    # - outer array is an array of cycles
    # - each inner array is an array of resources involved in a cycle
    # Short circuit resource evaluation if we detect cycle(s) in the graph. Mark
    # each corresponding resource as failed in the report before we fail to
    # ensure accurate reporting.
    graph_cycle_handler = lambda do |cycles|
      cycles.flatten.uniq.each do |resource|
        # We add a failed resource event to the status to ensure accurate
        # reporting through the event manager.
        resource_status(resource).fail_with_event(_('resource is part of a dependency cycle'))
      end
      raise Puppet::Error, _('One or more resource dependency cycles detected in graph')
    end

    # Generate the relationship graph, set up our generator to use it
    # for eval_generate, then kick off our traversal.
    generator.relationship_graph = relationship_graph
    progress = 0
    relationship_graph.traverse(:while => continue_while,
                                :pre_process => pre_process,
                                :overly_deferred_resource_handler => overly_deferred_resource_handler,
                                :canceled_resource_handler => canceled_resource_handler,
                                :graph_cycle_handler => graph_cycle_handler,
                                :teardown => teardown) do |resource|
      progress += 1
      if resource.is_a?(Puppet::Type::Component)
        Puppet.warning _("Somehow left a component in the relationship graph")
      else
        if Puppet[:evaltrace] && @catalog.host_config?
          resource.info _("Starting to evaluate the resource (%{progress} of %{total})") % { progress: progress, total: relationship_graph.size }

View on GitHub (pinned to e227c27540)

Solutions

  1. Run the agent with --graph and render the generated .dot files (graphviz dot -Tsvg *.dot) to see the cycle edges
  2. Read the run report: every resource in the cycle has the status 'resource is part of a dependency cycle' naming exactly the members
  3. Break the cycle by deleting one relationship edge, usually the redundant 'require => Class[...]' that containment already provides
  4. Replace anchor-style bidirectional ordering with 'contain' + single-direction 'before'/'require'
  5. For exported-resource rings, add staged/storeconfigs-friendly one-way edges instead of mutual notifies

Example fix

# before (cycle: a -> b -> a)
file { '/etc/app': require => File['/etc/app/conf.d'] }
file { '/etc/app/conf.d': require => File['/etc/app'] }

# after (single direction)
file { '/etc/app': before => File['/etc/app/conf.d'] }
file { '/etc/app/conf.d': }
Defensive patterns

Strategy: try-catch

Try / catch

begin
  apply_catalog(catalog)
rescue Puppet::Error => e
  raise if e.message !~ /dependency cycle/
  report.resource_statuses.values
       .select { |s| s.failed }
       .each { |s| warn "in cycle: #{s.resource}" }
end

Prevention

When it happens

Trigger: Resources A, B, C where A requires B, B requires C, and C requires A; class containment loops (class A includes B while B requires something in A); anchor/contain patterns that add both directions; explicit `require => Class['x']` combined with `Class['x'] { require => Class['y'] }` where y ultimately points back. Exported resources from other nodes can close the loop invisibly.

Common situations: Profile/module refactors that add 'require => Class[base]' while base already contains the caller; anchor-based ordering (anchor -> class -> anchor); rings formed through exported resources (nagios_service <-> host edges); a single typo'd before/require pair after a manifest split.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/5077cf2a1de346b7. Report an issue: GitHub.