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

  1. MKCOL the full parent directory chain for the Destination before issuing COPY.
  2. Confirm with PROPFIND that the destination parent exists and is a collection (isDir true).
  3. Use a Destination whose parent is a known existing directory.
  4. 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

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.