puppetlabs/puppet · error · Errno::EPERM
Operation not permitted
Error message
Operation not permitted
What it means
In Puppet's Windows file-system layer, recursive unlink logic dispatches per stat type: regular files go to File.unlink, symlinked directories to Dir.rmdir, but a real (non-symlink) directory cannot be removed with file semantics, so it raises Errno::EPERM with the file name. This is a guard against half-deleting a tree the caller meant to handle as directories.
Source
Thrown at lib/puppet/file_system/windows.rb:98
file_name = file_name.to_s # handle PathName
stat = begin
Puppet::Util::Windows::File.stat(file_name)
rescue
nil
end
# sigh, Ruby + Windows :(
if !stat
begin
::File.unlink(file_name)
rescue
Dir.rmdir(file_name)
end
elsif stat.ftype == 'directory'
if Puppet::Util::Windows::File.symlink?(file_name)
Dir.rmdir(file_name)
else
raise Errno::EPERM, file_name
end
else
::File.unlink(file_name)
end
end
file_names.length
end
def stat(path)
Puppet::Util::Windows::File.stat(path)
end
def lstat(path)
unless Puppet.features.manages_symlinks?
return Puppet::Util::Windows::File.stat(path)
end
View on GitHub (pinned to e227c27540)
Solutions
- Use directory-aware deletion: remove subdirectories with Dir.rmdir/recursive delete (FileUtils.rm_r) instead of file unlink
- Reorder operations: delete directory contents that are files, then remove directories explicitly bottom-up
- On Windows, test the path with File.directory? and branch before unlinking
Example fix
# before
entries.each { |e| Puppet::FileSystem.unlink(File.join(dir, e)) } # raises EPERM on dirs (Windows)
# after
require 'fileutils'
FileUtils.rm_r(dir) Defensive patterns
Strategy: validation
Validate before calling
entries = Dir.children(dir).map { |e| File.join(dir, e) }
files, dirs = entries.partition { |p| File.file?(p) || File.symlink?(p) }
# handle files with unlink APIs; handle dirs with rmdir/rm_r Type guard
def unlinkable?(path) File.file?(path) || File.symlink?(path) || (File.directory?(path) && File.symlink?(path)) end
Try / catch
begin Puppet::FileSystem.unlink(path) rescue Errno::EPERM raise unless Gem.win_platform? && File.directory?(path) && !File.symlink?(path) Dir.rmdir(path) # or FileUtils.rm_r for a populated tree end
Prevention
- Branch on File.directory? before unlink on Windows
- Prefer FileUtils.rm_r for whole trees instead of hand-rolled unlink walks
- Test cleanup helpers on Windows CI, not only POSIX
When it happens
Trigger: Invoking Puppet::FileSystem unlink/recursion APIs (e.g. clearing a directory's contents) on Windows where a subdirectory entry is encountered; deleting a mixed tree through a file-oriented API on Windows.
Common situations: Cross-platform cleanup code that works on POSIX (where unlink semantics differ) but hits subdirectories on Windows; cache/temp pruning walking into nested dirs.
Related errors
- Is a directory: %{directory}
- ReplaceFile(#{target}, #{source})
- MoveFileEx(#{source}, #{target}, #{flags.to_s(8)})
- GetFileAttributes(#{file_name})
- Failed to set file attributes
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/436cc55882d5be84.
Report an issue: GitHub.