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

Raised by Puppet::ModuleTool::Applications::Uninstaller (class Puppet::ModuleTool::Errors::LocalChangesError) when you uninstall a module whose files no longer match the checksums recorded for the installed release. Before deleting, the uninstaller runs Puppet::ModuleTool::Applications::Checksummer.run(mod.path) and refuses to destroy locally-modified content unless changes are explicitly ignored. The guard is skipped entirely when --force or --ignore-changes is passed, since @ignore_changes = options[:force] || options[:ignore_changes].

Source

Thrown at lib/puppet/module_tool/applications/uninstaller.rb:103

                  :module_name => @name
          end
        end
      end

      def validate_module
        mod = @installed.first

        unless @ignore_changes
          raise _("Either the `--ignore_changes` or `--force` argument must be specified to uninstall modules when running in FIPS mode.") if Puppet.runtime[:facter].value(:fips_enabled)

          changes = begin
            Puppet::ModuleTool::Applications::Checksummer.run(mod.path)
          rescue ArgumentError
            []
          end

          if mod.has_metadata? && !changes.empty?
            raise LocalChangesError,
                  :action => :uninstall,
                  :module_name => (mod.forge_name || mod.name).tr('/', '-'),
                  :requested_version => @options[:version],
                  :installed_version => mod.version
          end
        end

        if !@options[:force] && !mod.required_by.empty?
          raise ModuleIsRequiredError,
                :module_name => (mod.forge_name || mod.name).tr('/', '-'),
                :required_by => mod.required_by,
                :requested_version => @options[:version],
                :installed_version => mod.version
        end
      end
    end
  end
end

View on GitHub (pinned to e227c27540)

Solutions

  1. Re-run with `puppet module uninstall <name> --force` (or `--ignore-changes` if you only want to skip the checksum check but keep the dependents check)
  2. If the edits matter, copy the module directory aside first (cp -r) and then uninstall with --force
  3. Inspect what changed with `puppet module changes <module-dir>` and revert unintended edits, then uninstall without --force
  4. Restore pristine content by reinstalling the same release (`puppet module install <name> --version <v> --force`) and then uninstall cleanly

Example fix

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

# after
$ puppet module uninstall puppetlabs-stdlib --force
Defensive patterns

Strategy: validation

Validate before calling

require 'puppet/module_tool/applications/checksummer'

mod = env.modules.find { |m| (m.forge_name || m.name).tr('/', '-') == name }
if mod && mod.has_metadata?
  changes = begin
    Puppet::ModuleTool::Applications::Checksummer.run(mod.path)
  rescue ArgumentError
    []
  end
  abort "refusing: #{changes.length} locally changed files" unless changes.empty? || options[:force] || options[:ignore_changes]
end

Try / catch

result = Puppet::ModuleTool::Applications::Uninstaller.run(name, options)
if result[:error] && result[:error][:oneline] =~ /changes made locally/
  # decide: keep edits, back up, or retry with force: true
  FileUtils.cp_r(mod.path, "#{mod.path}.bak")
  retry_with = options.merge(force: true)
end

Prevention

When it happens

Trigger: Running `puppet module uninstall <author-module>` (or Uninstaller.new(name, options).run) where the target module has a valid metadata.json (has_metadata? true), Checksummer.run(mod.path) returns a non-empty change set, and neither --force/-f nor --ignore-changes/-c is set.

Common situations: A module from the Forge was hotfixed in place to work around a bug; files were touched by an editor, CI, or a sync tool (line-ending/CRLF rewrites on Windows); an older r10k/librarian-puppet checkout left the tree dirty; permissions or ownership changes alter file digests.

Related errors


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