{"record":{"id":"c584ea894e672a97","repo":"payloadcms/payload","slug":"there-was-an-error-deleting-file","errorCode":null,"errorMessage":"There was an error deleting file.","messagePattern":"There was an error deleting file\\.","errorType":"http","errorClass":"ErrorDeletingFile","httpStatus":500,"severity":"error","filePath":"packages/payload/src/uploads/deleteAssociatedFiles.ts","lineNumber":40,"sourceCode":"  doc,\n  files = [],\n  overrideDelete,\n  req,\n}) => {\n  if (!collectionConfig.upload) {\n    return\n  }\n  if (overrideDelete || files.length > 0) {\n    const { staticDir: staticPath } = collectionConfig.upload\n\n    const fileToDelete = `${staticPath}/${doc.filename as string}`\n\n    try {\n      if (await fileExists(fileToDelete)) {\n        await fs.unlink(fileToDelete)\n      }\n    } catch (ignore) {\n      throw new ErrorDeletingFile(req.t)\n    }\n\n    if (doc.sizes) {\n      const sizes: FileData[] = Object.values(doc.sizes)\n      // Since forEach will not wait until unlink is finished it could\n      // happen that two operations will try to delete the same file.\n      // To avoid this it is recommended to use \"sync\" instead\n\n      for (const size of sizes) {\n        const sizeToDelete = `${staticPath}/${size.filename}`\n        try {\n          if (await fileExists(sizeToDelete)) {\n            await fs.unlink(sizeToDelete)\n          }\n        } catch (ignore) {\n          throw new ErrorDeletingFile(req.t)\n        }\n      }","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/payload/src/uploads/deleteAssociatedFiles.ts#L22-L58","documentation":"ErrorDeletingFile thrown when fs.unlink of the primary uploaded file fails despite fileExists returning true. Thrown inside deleteAssociatedFiles, which runs when a document with an upload field is deleted or its file is replaced (overrideDelete or files.length > 0).","triggerScenarios":"Deleting or updating a media document where the main file at `${staticDir}/${doc.filename}` exists but unlink rejects — EACCES (permissions), EBUSY/EPERM on Windows, stale NFS handle, or the file removed by another process between the exists check and unlink.","commonSituations":"staticDir written by a different OS user than the Node process (permission mismatch); Docker volume mounted read-only or with wrong uid:gid; NFS/SMB mount hiccup; antivirus/file-lock on Windows locking the file; manual cleanup script racing with the delete request.","solutions":["Verify the Node process has write/delete permission on staticDir and the file (check uid:gid, chmod).","For Docker, ensure the volume is mounted read-write with matching PUID/PGID.","Retry the document delete after clearing any file lock (close editors/AV scans on Windows).","If files are routinely removed out-of-band, consider a custom upload handler that tolerates missing files instead of throwing."],"exampleFix":"// before — throws if file locked\nawait fs.unlink(fileToDelete)\n// after — tolerate already-gone files, surface others\ntry {\n  await fs.unlink(fileToDelete)\n} catch (e) {\n  if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e\n}","handlingStrategy":"try-catch","validationCode":"import { access, constants } from 'fs/promises'\nasync function canDelete(p: string): Promise<boolean> {\n  try { await access(p, constants.W_OK); return true } catch { return false }\n}\n// before delete:\nif (doc.filename && !(await canDelete(`${staticDir}/${doc.filename}`))) {\n  fixPermissionsOrWarn(staticDir)\n}","typeGuard":"const isDeletableError = (e: NodeJS.ErrnoException): boolean =>\n    ['EACCES', 'EPERM', 'EBUSY'].includes(e.code ?? '')","tryCatchPattern":"try {\n  await payload.delete({ collection: 'media', id })\n} catch (e) {\n  if (e.name === 'ErrorDeletingFile') {\n    logger.error('Could not delete file on disk; document was deleted')\n    // optionally queue a cleanup job\n  } else throw e\n}","preventionTips":["Run the Node process as the owner of staticDir.","Set group-write + same gid for uploader/worker processes.","Avoid antivirus/file-locking on Windows for the media dir.","Periodically reconcile DB records with disk to detect orphans."],"tags":["upload","filesystem","delete","permissions"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}