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

  1. Check the path on the node: if a directory occupies it, remove or rename it (`Remove-Item -Recurse`)
  2. Correct the resource design so files and directories never share one path
  3. 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

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


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