HeyPuter/puter · error · HttpError
dest_is_not_a_directory
dest_is_not_a_directory
Error message
Destination parent missing or not a directory
What it means
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.
Source
Thrown at src/backend/controllers/webdav/WebDAVController.ts:666
const source = await this.stores.fsEntry.getEntryByPath(davPath);
if (!source)
throw new HttpError(404, 'Source not found', {
legacyCode: 'not_found',
});
const overwrite = req.headers.overwrite !== 'F';
const destExists = await this.stores.fsEntry.getEntryByPath(destPath);
if (destExists && !overwrite)
throw new HttpError(412, 'Destination exists and Overwrite=F', {
legacyCode: 'conflict',
});
const destParent = await this.stores.fsEntry.getEntryByPath(
pathPosix.dirname(destPath),
);
if (!destParent?.isDir)
throw new HttpError(
409,
'Destination parent missing or not a directory',
{ legacyCode: 'dest_is_not_a_directory' },
);
const copy = await this.services.fs.copy(userId, {
source,
destinationParent: destParent,
newName: pathPosix.basename(destPath),
overwrite,
});
this.#emitGuiEvent('outer.gui.item.added', copy);
res.status(destExists ? 204 : 201).end();
}
// -- MOVE --------------------------------------------------------
async #move(View on GitHub (pinned to 908ec23eda)
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.
Example fix
// before COPY /src.txt HTTP/1.1 Destination: /new-dir/sub/file.txt // after MKCOL /new-dir HTTP/1.1 MKCOL /new-dir/sub HTTP/1.1 COPY /src.txt HTTP/1.1 Destination: /new-dir/sub/file.txt
Defensive patterns
Strategy: validation
Validate before calling
const parent = pathPosix.dirname(destPath);
const parentEntry = await webdavPropfind(parent).catch(() => null);
if (!parentEntry?.isCollection) {
// MKCOL the chain first
await mkcolRecursive(parent);
} Try / catch
try {
await webdavCopy(src, dest, headers);
} catch (e) {
if (e.status === 409) { await mkcolRecursive(pathPosix.dirname(dest)); await webdavCopy(src, dest, headers); }
else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/d8b5513e446f630e.
Report an issue: GitHub.