{"record":{"id":"c1bb06ecf02d23dc","repo":"puppetlabs/puppet","slug":"is-a-directory-directory-c1bb06","errorCode":null,"errorMessage":"Is a directory: %{directory}","messagePattern":"Is a directory: %(.+?)","errorType":"exception","errorClass":"Errno::EISDIR","httpStatus":null,"severity":"error","filePath":"lib/puppet/util.rb","lineNumber":677,"sourceCode":"      end\n\n      if Puppet::Util::Platform.windows?\n        # Windows ReplaceFile needs a file to exist, so touch handles this\n        unless Puppet::FileSystem.exist?(file)\n          Puppet::FileSystem.touch(file)\n          if mode\n            Puppet::Util::Windows::Security.set_mode(mode, Puppet::FileSystem.path_string(file))\n          end\n        end\n        # Yes, the arguments are reversed compared to the rename in the rest\n        # of the world.\n        Puppet::Util::Windows::File.replace_file(FileSystem.path_string(file), tempfile.path)\n\n      else\n        # MRI Ruby checks for this and raises an error, while JRuby removes the directory\n        # and replaces it with a file. This makes the our version of replace_file() consistent\n        if Puppet::FileSystem.exist?(file) && Puppet::FileSystem.directory?(file)\n          raise Errno::EISDIR, _(\"Is a directory: %{directory}\") % { directory: file }\n        end\n\n        File.rename(tempfile.path, Puppet::FileSystem.path_string(file))\n      end\n    ensure\n      # in case an error occurred before we renamed the temp file, make sure it\n      # gets deleted\n      if tempfile\n        tempfile.close!\n      end\n    end\n\n    # Ideally, we would now fsync the directory as well, but Ruby doesn't\n    # have support for that, and it doesn't matter /that/ much...\n\n    # Return something true, and possibly useful.\n    file\n  end","sourceCodeStart":659,"sourceCodeEnd":695,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util.rb#L659-L695","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nPuppet::Util.replace_file('/etc/puppetlabs/puppet/puppet.conf', 0o644) { |f| f.write(data) }\n# raises Errno::EISDIR when puppet.conf is a directory\n\n# after\nrequire 'fileutils'\ntarget = '/etc/puppetlabs/puppet/puppet.conf'\nFileUtils.rm_rf(target) if File.directory?(target)\nPuppet::Util.replace_file(target, 0o644) { |f| f.write(data) }","handlingStrategy":"validation","validationCode":"raise Errno::EISDIR.new(target) if File.directory?(target)  # fail fast with your own context\nFileUtils.rm_rf(target) if File.directory?(target)            # or clear it deliberately","typeGuard":null,"tryCatchPattern":"begin\n  Puppet::Util.replace_file(target, 0o644) { |f| f.write(data) }\nrescue Errno::EISDIR => e\n  raise Puppet::Error, \"#{target} is a directory; remove it (rm -rf) and rerun: #{e.message}\"\nend","preventionTips":["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"],"tags":["puppet","filesystem","atomic-write","eisdir"],"backgroundTag":"eisdir-write-to-directory","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}