{"record":{"id":"d8b5513e446f630e","repo":"HeyPuter/puter","slug":"dest-is-not-a-directory-d8b551","errorCode":"dest_is_not_a_directory","errorMessage":"Destination parent missing or not a directory","messagePattern":"Destination parent missing or not a directory","errorType":"http","errorClass":"HttpError","httpStatus":409,"severity":"error","filePath":"src/backend/controllers/webdav/WebDAVController.ts","lineNumber":666,"sourceCode":"\n        const source = await this.stores.fsEntry.getEntryByPath(davPath);\n        if (!source)\n            throw new HttpError(404, 'Source not found', {\n                legacyCode: 'not_found',\n            });\n\n        const overwrite = req.headers.overwrite !== 'F';\n        const destExists = await this.stores.fsEntry.getEntryByPath(destPath);\n        if (destExists && !overwrite)\n            throw new HttpError(412, 'Destination exists and Overwrite=F', {\n                legacyCode: 'conflict',\n            });\n\n        const destParent = await this.stores.fsEntry.getEntryByPath(\n            pathPosix.dirname(destPath),\n        );\n        if (!destParent?.isDir)\n            throw new HttpError(\n                409,\n                'Destination parent missing or not a directory',\n                { legacyCode: 'dest_is_not_a_directory' },\n            );\n\n        const copy = await this.services.fs.copy(userId, {\n            source,\n            destinationParent: destParent,\n            newName: pathPosix.basename(destPath),\n            overwrite,\n        });\n        this.#emitGuiEvent('outer.gui.item.added', copy);\n        res.status(destExists ? 204 : 201).end();\n    }\n\n    // -- MOVE --------------------------------------------------------\n\n    async #move(","sourceCodeStart":648,"sourceCodeEnd":684,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/webdav/WebDAVController.ts#L648-L684","documentation":"Thrown by the WebDAV COPY handler when the destination's parent directory either does not exist or is not a directory (destParent is null or destParent.isDir is false). RFC 4918 §9.8.5 mandates a 409 Conflict in this situation — copy cannot create intermediate directories.","triggerScenarios":"A COPY whose Destination path's parent has not been created yet (e.g. copying to /a/b/c.txt when /a/b does not exist), or whose parent resolves to a file rather than a directory.","commonSituations":"Copying into a freshly chosen folder that was never MKCOL'd; destination parent was deleted between PROPFIND and COPY; path component collided with an existing file name; client assumed recursive folder creation.","solutions":["MKCOL the full parent directory chain for the Destination before issuing COPY.","Confirm with PROPFIND that the destination parent exists and is a collection (isDir true).","Use a Destination whose parent is a known existing directory.","If the parent name collides with a file, rename the conflicting file or choose a different parent."],"exampleFix":"// before\nCOPY /src.txt HTTP/1.1\nDestination: /new-dir/sub/file.txt\n\n// after\nMKCOL /new-dir HTTP/1.1\nMKCOL /new-dir/sub HTTP/1.1\nCOPY /src.txt HTTP/1.1\nDestination: /new-dir/sub/file.txt","handlingStrategy":"validation","validationCode":"const parent = pathPosix.dirname(destPath);\nconst parentEntry = await webdavPropfind(parent).catch(() => null);\nif (!parentEntry?.isCollection) {\n  // MKCOL the chain first\n  await mkcolRecursive(parent);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await webdavCopy(src, dest, headers);\n} catch (e) {\n  if (e.status === 409) { await mkcolRecursive(pathPosix.dirname(dest)); await webdavCopy(src, dest, headers); }\n  else throw e;\n}","preventionTips":["MKCOL the full parent chain of the destination before COPY.","PROPFIND the parent to confirm it is a collection (isDir) before copying.","Do not assume recursive directory creation — WebDAV COPY does not create intermediates.","Resolve parent/child name collisions before they become 409s."],"tags":["webdav","http","copy","filesystem","conflict","destination"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}