puppetlabs/puppet · error · Puppet::ModuleTool::Errors::LocalChangesError

Could not %{action} '%{module_name}'; module has had changes

Error message

Could not %{action} '%{module_name}'; module has had changes made locally

What it means

The upgrade-path twin of the uninstall LocalChangesError: Puppet::ModuleTool::Errors::LocalChangesError raised by the Upgrader when the installed module's files differ from its release checksums. Before replacing a module, Puppet wants confirmation you will not lose edits; @ignore_changes is options[:force] || options[:ignore_changes], so either CLI flag suppresses the check. Note the bare `rescue` around Checksummer.run means modules without parseable checksums are treated as unchanged.

Source

Thrown at lib/puppet/module_tool/applications/upgrader.rb:85

          # (or something similar).
          def installed_release.priority
            0
          end

          mod = installed_release.mod
          results[:installed_version] = SemanticPuppet::Version.parse(mod.version)
          dir = Pathname.new(mod.modulepath)

          vstring = mod.version ? "v#{mod.version}" : '???'
          Puppet.notice _("Found '%{name}' (%{version}) in %{dir} ...") % { name: name, version: colorize(:cyan, vstring), dir: dir }
          unless @ignore_changes
            changes = begin
              Checksummer.run(mod.path)
            rescue
              []
            end
            if mod.has_metadata? && !changes.empty?
              raise LocalChangesError,
                    :action => :upgrade,
                    :module_name => name,
                    :requested_version => results[:requested_version],
                    :installed_version => mod.version
            end
          end

          Puppet::Forge::Cache.clean

          # Ensure that there is at least one candidate release available
          # for the target package.
          available_versions = module_repository.fetch(name)
          if available_versions.empty?
            raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host)
          elsif results[:requested_version] != :latest
            requested = Puppet::Module.parse_range(results[:requested_version])
            unless available_versions.any? { |m| requested.include? m.version }
              raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host)

View on GitHub (pinned to e227c27540)

Solutions

  1. Re-run with `puppet module upgrade <name> --ignore-changes` to keep only the checksum check suppressed, or --force to also relax graph constraints
  2. Move your patches into a fork or a wrapper module so the Forge module stays pristine
  3. Check what changed via `puppet module changes <module-dir>` and revert unintended modifications, then upgrade normally
  4. Manage the environment with a Puppetfile + r10k so upgrades happen through version control instead of in place

Example fix

# before
$ puppet module upgrade puppetlabs-stdlib
# Error: Could not upgrade 'puppetlabs-stdlib'; module has had changes made locally

# after
$ puppet module upgrade puppetlabs-stdlib --ignore-changes
Defensive patterns

Strategy: validation

Validate before calling

mod = env.modules.find { |m| m.forge_name && m.forge_name.tr('/', '-') == name }
if mod && mod.has_metadata? && !options[:force] && !options[:ignore_changes]
  changes = begin
    Puppet::ModuleTool::Applications::Checksummer.run(mod.path)
  rescue StandardError
    []
  end
  abort 'local edits present — commit them to a fork or pass --ignore-changes' unless changes.empty?
end

Try / catch

result = Puppet::ModuleTool::Applications::Upgrader.new(name, options).run
if result[:error] && result[:error][:oneline] =~ /changes made locally/
  result = Puppet::ModuleTool::Applications::Upgrader
    .new(name, options.merge(ignore_changes: true)).run
end

Prevention

When it happens

Trigger: Running `puppet module upgrade <author-module>` without --force/-f or --ignore-changes/-c when the installed module has metadata and Checksummer.run(mod.path) reports differences (line 78-90 of upgrader.rb).

Common situations: Patching Forge modules in place instead of forking; editor/CRLF or permission churn altering digests; content modified by older tooling; CI pipelines that mutate module files before running upgrades.

Related errors


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