{"record":{"id":"25be60e5e7fdc08a","repo":"puppetlabs/puppet","slug":"will-not-remove-directory-backup-newfile-use-a","errorCode":null,"errorMessage":"Will not remove directory backup %{newfile}; use a filebucket","messagePattern":"Will not remove directory backup %(.+?); use a filebucket","errorType":"exception","errorClass":"Puppet::Error","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/backups.rb","lineNumber":69,"sourceCode":"      self.fail Puppet::Error, _(\"Could not back %{file} up: %{message}\") % { file: file, message: detail.message }, detail\n    end\n  end\n\n  def remove_backup(newfile)\n    if instance_of?(Puppet::Type::File) and self[:links] != :follow\n      method = :lstat\n    else\n      method = :stat\n    end\n\n    begin\n      stat = Puppet::FileSystem.send(method, newfile)\n    rescue Errno::ENOENT\n      return\n    end\n\n    if stat.ftype == \"directory\"\n      raise Puppet::Error, _(\"Will not remove directory backup %{newfile}; use a filebucket\") % { newfile: newfile }\n    end\n\n    info _(\"Removing old backup of type %{file_type}\") % { file_type: stat.ftype }\n\n    begin\n      Puppet::FileSystem.unlink(newfile)\n    rescue => detail\n      message = _(\"Could not remove old backup: %{detail}\") % { detail: detail }\n      log_exception(detail, message)\n      self.fail Puppet::Error, message, detail\n    end\n  end\n\n  def backup_file_with_filebucket(f)\n    sum = bucket.backup(f)\n    info _(\"Filebucketed %{f} to %{filebucket} with sum %{sum}\") % { f: f, filebucket: bucket.name, sum: sum }\n    sum\n  end","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/backups.rb#L51-L87","documentation":"Puppet's file resource makes local backups by copying the managed path to path + backup suffix (default 'puppet.bak') before replacing it. Before writing the new backup, perform_backup_with_backuplocal calls remove_backup to unlink the previous one. If the backup target exists and its ftype is 'directory', remove_backup raises Puppet::Error at lib/puppet/util/backups.rb:69 because Puppet refuses to recursively delete a whole directory tree just to rotate a backup; the message points at filebuckets as the supported alternative for backing up directories.","triggerScenarios":"A file resource with a local backup suffix (backup => '.bak' or the default) whose target path+backup already exists as a directory on the node: someone ran `mkdir /etc/motd.puppet.bak`, a previous run used FileUtils.cp_r (which copies directories too, see backups.rb:46) and left a directory backup that the next run now refuses to remove, or a restore/rename left a directory at exactly that name.","commonSituations":"Converging a node where an operator manually created a directory with the backup name; a resource that changed between file and directory content while keeping local suffix backups; leftovers from an interrupted or older-format run; path prefixes chosen so path+backup collides with an unrelated directory.","solutions":["Remove or rename the directory at {path}{backup} (e.g. `rm -rf /etc/motd.puppet.bak`) so the next Puppet run can recreate a regular file backup","Switch the resource to filebucket backup: declare a `filebucket { 'main': server => ... }` resource and set the file's `backup` parameter to the bucket name, which is what the error message recommends","Set `backup => false` on the file resource if no backup is needed at all","Audit other file resources on the node for the same backup-target collision before the next agent run"],"exampleFix":"# before\nfile { '/etc/motd':\n  ensure  => file,\n  source  => 'puppet:///modules/motd/motd',\n  backup  => '.bak',   # fails when /etc/motd.bak is a directory\n}\n\n# after\nfilebucket { 'main':\n  server => 'puppet.example.com',\n}\nfile { '/etc/motd':\n  ensure  => file,\n  source  => 'puppet:///modules/motd/motd',\n  backup  => 'main',   # filebucket handles directories safely\n}","handlingStrategy":"validation","validationCode":"# Before letting Puppet manage the file, verify the local backup target is not a directory\npath    = '/etc/motd'\nsuffix = '.puppet.bak'   # or the resource's backup parameter\nbackup_target = path + suffix\n\nif File.exist?(backup_target) && File.ftype(backup_target) == 'directory'\n  abort \"Refusing to run: #{backup_target} is a directory; Puppet would fail with 'Will not remove directory backup'. Remove it or use a filebucket.\"\nend","typeGuard":null,"tryCatchPattern":"# In Ruby code driving Puppet (e.g. rake tasks, orchestration), fail gracefully instead of letting the agent run abort:\nbegin\n  # apply the catalog / run the resource\nrescue Puppet::Error => e\n  if e.message.include?('Will not remove directory backup')\n    target = e.message[/Will not remove directory backup ([^;]+);/, 1]\n    warn \"Backup target #{target} is a directory - remove it or switch the resource to a filebucket\"\n  else\n    raise\n  end\nend","preventionTips":["Prefer filebucket backups (backup => <bucket name>) over local suffix backups on servers where directories may share the name space","Never create directories whose name equals path + backup suffix of a managed file","Set backup => false on file resources that do not need backups to shrink the failure surface","In CI for manifests, lint for local backup suffixes on file resources that manage directories"],"tags":["puppet","file-resource","backup","filebucket","filesystem"],"backgroundTag":"backup-target-conflict","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}