puppetlabs/puppet · error · Errno::EISDIR
Is a directory: %{directory}
Error message
Is a directory: %{directory} What it means
Puppet::FileSystem::Windows#replace_file refuses to atomically replace a path that is a directory, raising Errno::EISDIR with the path, mirroring POSIX rename semantics. The subsequent logic (DACL/inheritance handling) only makes sense for files, so a directory destination is rejected up front.
Source
Thrown at lib/puppet/file_system/windows.rb:140
end
def read_preserve_line_endings(path)
contents = path.read(:mode => 'rb', :encoding => 'bom|utf-8')
contents = path.read(:mode => 'rb', :encoding => "bom|#{Encoding.default_external.name}") unless contents.valid_encoding?
contents = path.read unless contents.valid_encoding?
contents
end
# https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--0-499-
FILE_NOT_FOUND = 2
ACCESS_DENIED = 5
SHARING_VIOLATION = 32
LOCK_VIOLATION = 33
def replace_file(path, mode = nil)
if directory?(path)
raise Errno::EISDIR, _("Is a directory: %{directory}") % { directory: path }
end
current_sid = Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name)
current_sid ||= Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_sam_compatible_user_name)
dacl = case mode
when 0o644
dacl = secure_dacl(current_sid)
dacl.allow(Puppet::Util::Windows::SID::BuiltinUsers, FILE_READ)
dacl
when 0o660, 0o640, 0o600, 0o440
secure_dacl(current_sid)
when nil
get_dacl_from_file(path) || secure_dacl(current_sid)
else
raise ArgumentError, "#{mode} is invalid: Only modes 0644, 0640, 0660, and 0440 are allowed"
end
View on GitHub (pinned to e227c27540)
Solutions
- Check the path on the node: if a directory occupies it, remove or rename it (`Remove-Item -Recurse`)
- Correct the resource design so files and directories never share one path
- If the directory is managed by Puppet, ensure => absent it (as a directory) in a preceding step before writing the file
Example fix
# before (manifest)
file { 'C:/opt/app/state': ensure => directory }
file { 'C:/opt/app/state': ensure => file, source => 'puppet:///modules/app/state' }
# after
file { 'C:/opt/app/state': ensure => absent, force => true }
file { 'C:/opt/app/state.txt': ensure => file, source => 'puppet:///modules/app/state' } Defensive patterns
Strategy: validation
Validate before calling
if Puppet::FileSystem.directory?(path)
raise ArgumentError, "#{path} is a directory; cannot replace with a file"
end
Puppet::FileSystem.replace_file(path, 0o644) { |f| f.write(data) } Type guard
def replaceable_path?(path) !Puppet::FileSystem.exist?(path) || Puppet::FileSystem.file?(path) end
Try / catch
begin
Puppet::FileSystem.replace_file(path, 0o644) { |f| f.write(data) }
rescue Errno::EISDIR => e
raise "#{path} occupied by a directory (#{e.message}); remove it or pick a new file name"
end Prevention
- Keep a naming scheme where directories and files never collide (extensions on files)
- Audit installer-created placeholder folders before writing config into their paths
- Remember Windows paths are case-insensitive: collisions can hide behind casing
When it happens
Trigger: An atomic file write on Windows targeting a path that already exists as a directory: config/state files whose path collides with a folder created by an installer or another resource; Puppet manifests writing files where ensure directory was previously applied.
Common situations: A file resource path that was changed from a directory to a file without cleaning up; installers creating placeholder directories; case-insensitive path collisions on Windows.
Related errors
- Is a directory: %{directory}
- Operation not permitted
- Is a directory: %{directory}
- ReplaceFile(#{target}, #{source})
- MoveFileEx(#{source}, #{target}, #{flags.to_s(8)})
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/84899e2714eeec52.
Report an issue: GitHub.