puppetlabs/puppet · error · Puppet::Error

Catalog environment didn't stabilize after %{tries} fetches,

Error message

Catalog environment didn't stabilize after %{tries} fetches, aborting run

What it means

Raised by `Puppet::Configurer` when the environment in the fetched catalog repeatedly differs from the environment the agent used for that fetch. Because facts can influence which catalog and environment the server selects, the agent switches to the server-assigned environment and refetches; after 4 such switches (the guard is `tries > 3`) it aborts to prevent an infinite fetch loop. In practice some fact keeps flipping the node between two or more environments on each round.

Source

Thrown at lib/puppet/configurer.rb:448

      catalog = prepare_and_retrieve_catalog(cached_catalog, facts, options, query_options)
      unless catalog
        return nil
      end

      if Puppet[:strict_environment_mode] && catalog.environment != @environment
        Puppet.err _("Not using catalog because its environment '%{catalog_env}' does not match agent specified environment '%{local_env}' and strict_environment_mode is set") % { catalog_env: catalog.environment, local_env: @environment }
        return nil
      end

      # Here we set the local environment based on what we get from the
      # catalog. Since a change in environment means a change in facts, and
      # facts may be used to determine which catalog we get, we need to
      # rerun the process if the environment is changed.
      tries = 0
      while catalog.environment and !catalog.environment.empty? and catalog.environment != @environment
        if tries > 3
          raise Puppet::Error, _("Catalog environment didn't stabilize after %{tries} fetches, aborting run") % { tries: tries }
        end

        Puppet.notice _("Local environment: '%{local_env}' doesn't match server specified environment '%{catalog_env}', restarting agent run with environment '%{catalog_env}'") % { local_env: @environment, catalog_env: catalog.environment }
        @environment = catalog.environment
        report.environment = @environment

        push_current_environment_and_loaders

        query_options, facts = get_facts(options)
        query_options[:configured_environment] = configured_environment

        # if we get here, ignore the cached catalog
        catalog = prepare_and_retrieve_catalog(nil, facts, options, query_options)
        return nil unless catalog

        tries += 1
      end

View on GitHub (pinned to e227c27540)

Solutions

  1. Pin one environment: set `environment = <env>` in the agent's puppet.conf and remove fact/ENC-based environment overrides
  2. Move environment-dependent external facts out of per-environment directories so fact values stay constant across environment switches
  3. Inspect the ENC/classifier output for the node and make the assigned environment stable
  4. Diagnose with `puppet agent --test` and read the 'Local environment ... doesn't match server specified environment' notices to see which two environments are alternating

Example fix

# before: external fact reads from the *current* environment's directory
Facter.add(:env_hint) do
  setcode { File.read("/etc/puppetlabs/code/environments/#{Facter.value(:environment)}/hint") }
end
# after: static, environment-independent fact + pinned agent environment
Facter.add(:env_hint) { setcode { 'production' } }
# puppet.conf: [main] environment = production
Defensive patterns

Strategy: validation

Validate before calling

# before a run, confirm the configured environment matches what the server assigns
configured = Puppet[:environment]
assigned  = Puppet::Node.indirection.find(Puppet[:certname])&.environment.to_s
if !assigned.empty? && assigned != configured
  Puppet.warning "server will assign '#{assigned}' but agent is configured for '#{configured}' — pin one environment"
end

Prevention

When it happens

Trigger: The `while catalog.environment != @environment` loop re-runs get_facts and a catalog fetch, and the server assigns a different environment each iteration: an external/custom fact whose value depends on the active environment (e.g. it reads a file under the environment's own directory), an ENC that derives environment from a flapping fact, or a classifier regex matching different facts on each pass.

Common situations: External facts placed inside the per-environment pluginfacts path so they change whenever the environment changes; ENC returning environment based on hostname or a custom fact that itself depends on environment; agent puppet.conf left at 'production' while classification insists on another environment.

Related errors


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