puppetlabs/puppet · error · Errno::EISDIR
Is a directory: %{directory}
Error message
Is a directory: %{directory} What it means
Puppet::Util.replace_file writes content to a tempfile then atomically renames it over the destination; on POSIX (the non-Windows branch) it explicitly checks whether the destination exists and is a directory and raises Errno::EISDIR with this message. MRI Ruby's own File.rename would raise EISDIR anyway; Puppet raises it early so JRuby (which would delete the directory) behaves the same. Nearly every atomic config write in Puppet (puppet.conf, agent state files, SSL certs) funnels through this helper.
Source
Thrown at lib/puppet/util.rb:677
end
if Puppet::Util::Platform.windows?
# Windows ReplaceFile needs a file to exist, so touch handles this
unless Puppet::FileSystem.exist?(file)
Puppet::FileSystem.touch(file)
if mode
Puppet::Util::Windows::Security.set_mode(mode, Puppet::FileSystem.path_string(file))
end
end
# Yes, the arguments are reversed compared to the rename in the rest
# of the world.
Puppet::Util::Windows::File.replace_file(FileSystem.path_string(file), tempfile.path)
else
# MRI Ruby checks for this and raises an error, while JRuby removes the directory
# and replaces it with a file. This makes the our version of replace_file() consistent
if Puppet::FileSystem.exist?(file) && Puppet::FileSystem.directory?(file)
raise Errno::EISDIR, _("Is a directory: %{directory}") % { directory: file }
end
File.rename(tempfile.path, Puppet::FileSystem.path_string(file))
end
ensure
# in case an error occurred before we renamed the temp file, make sure it
# gets deleted
if tempfile
tempfile.close!
end
end
# Ideally, we would now fsync the directory as well, but Ruby doesn't
# have support for that, and it doesn't matter /that/ much...
# Return something true, and possibly useful.
file
endView on GitHub (pinned to e227c27540)
Solutions
- Remove or rename the offending directory: `rm -rf /etc/puppetlabs/puppet/puppet.conf` (verify it is truly a directory with `ls -ld` first)
- Audit manifests/provisioning for mkdir -p applied to file paths
- If calling replace_file yourself, pre-check File.directory?(target) and raise a clear application error
Example fix
# before
Puppet::Util.replace_file('/etc/puppetlabs/puppet/puppet.conf', 0o644) { |f| f.write(data) }
# raises Errno::EISDIR when puppet.conf is a directory
# after
require 'fileutils'
target = '/etc/puppetlabs/puppet/puppet.conf'
FileUtils.rm_rf(target) if File.directory?(target)
Puppet::Util.replace_file(target, 0o644) { |f| f.write(data) } Defensive patterns
Strategy: validation
Validate before calling
raise Errno::EISDIR.new(target) if File.directory?(target) # fail fast with your own context FileUtils.rm_rf(target) if File.directory?(target) # or clear it deliberately
Try / catch
begin
Puppet::Util.replace_file(target, 0o644) { |f| f.write(data) }
rescue Errno::EISDIR => e
raise Puppet::Error, "#{target} is a directory; remove it (rm -rf) and rerun: #{e.message}"
end Prevention
- Never mkdir -p a path that is a file target (puppet.conf, state yaml, CSR files)
- Health-check nodes with `find /etc/puppetlabs -type d -name '*.conf'` after provisioning
- In custom code, always check File.directory?(dest) before replace_file
When it happens
Trigger: Any Puppet write path where the target file path is occupied by a directory: someone ran mkdir /etc/puppetlabs/puppet/puppet.conf; a state file like last_run_summary.yaml replaced by a directory; calling Puppet::Util.replace_file(dir_path, mode) directly in custom code.
Common situations: Broken provisioning that mkdir -p's file paths; artifacts left by interrupted installs; operators 'protecting' a file by turning it into a directory; manifests that declare file { path: ensure => directory } colliding with Puppet's internal writes.
Related errors
- Is a directory: %{directory}
- Is a directory: %{directory}
- Could not destroy %{json} %{request}: %{detail}
- Could not read JSON data for %{name} %{key}: %{detail}
- Could not destroy %{name} %{request}: %{detail}
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/c1bb06ecf02d23dc.
Report an issue: GitHub.