{"record":{"id":"527981cdf8cf5edf","repo":"puppetlabs/puppet","slug":"catalog-environment-didn-t-stabilize-after-tries","errorCode":null,"errorMessage":"Catalog environment didn't stabilize after %{tries} fetches, aborting run","messagePattern":"Catalog environment didn't stabilize after %(.+?) fetches, aborting run","errorType":"exception","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/configurer.rb","lineNumber":448,"sourceCode":"\n      catalog = prepare_and_retrieve_catalog(cached_catalog, facts, options, query_options)\n      unless catalog\n        return nil\n      end\n\n      if Puppet[:strict_environment_mode] && catalog.environment != @environment\n        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 }\n        return nil\n      end\n\n      # Here we set the local environment based on what we get from the\n      # catalog. Since a change in environment means a change in facts, and\n      # facts may be used to determine which catalog we get, we need to\n      # rerun the process if the environment is changed.\n      tries = 0\n      while catalog.environment and !catalog.environment.empty? and catalog.environment != @environment\n        if tries > 3\n          raise Puppet::Error, _(\"Catalog environment didn't stabilize after %{tries} fetches, aborting run\") % { tries: tries }\n        end\n\n        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 }\n        @environment = catalog.environment\n        report.environment = @environment\n\n        push_current_environment_and_loaders\n\n        query_options, facts = get_facts(options)\n        query_options[:configured_environment] = configured_environment\n\n        # if we get here, ignore the cached catalog\n        catalog = prepare_and_retrieve_catalog(nil, facts, options, query_options)\n        return nil unless catalog\n\n        tries += 1\n      end\n","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/configurer.rb#L430-L466","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pin one environment: set `environment = <env>` in the agent's puppet.conf and remove fact/ENC-based environment overrides","Move environment-dependent external facts out of per-environment directories so fact values stay constant across environment switches","Inspect the ENC/classifier output for the node and make the assigned environment stable","Diagnose with `puppet agent --test` and read the 'Local environment ... doesn't match server specified environment' notices to see which two environments are alternating"],"exampleFix":"# before: external fact reads from the *current* environment's directory\nFacter.add(:env_hint) do\n  setcode { File.read(\"/etc/puppetlabs/code/environments/#{Facter.value(:environment)}/hint\") }\nend\n# after: static, environment-independent fact + pinned agent environment\nFacter.add(:env_hint) { setcode { 'production' } }\n# puppet.conf: [main] environment = production","handlingStrategy":"validation","validationCode":"# before a run, confirm the configured environment matches what the server assigns\nconfigured = Puppet[:environment]\nassigned  = Puppet::Node.indirection.find(Puppet[:certname])&.environment.to_s\nif !assigned.empty? && assigned != configured\n  Puppet.warning \"server will assign '#{assigned}' but agent is configured for '#{configured}' — pin one environment\"\nend","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep external facts out of per-environment directories so they cannot change with the environment","Make ENC/classifier environment assignment deterministic, not fact-derived","Watch for the 'Local environment ... doesn't match server specified environment' notice — it is the early warning of the loop"],"tags":["puppet-agent","environment","catalog","loop-guard","facts"],"backgroundTag":"catalog-environment-mismatch","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}