neoclide/coc.nvim · error
${dest} exists, but not directory!
Error message
${dest} exists, but not directory! What it means
After resolving dest, download() stats it if it already exists. If the path exists but is a regular file (or other non-directory), the download destination is unusable and it throws '<dest> exists, but not directory!'. dest is treated as the target directory for the downloaded (and possibly extracted) content.
Source
Thrown at src/model/download.ts:231
for (let [name, value] of Object.entries({
maxDownloadSize: options.maxDownloadSize,
maxExtractSize: options.maxExtractSize,
maxArchiveEntries: options.maxArchiveEntries
})) {
if (value !== undefined && (!Number.isFinite(value) || value <= 0)) throw new Error(`${name} must be a positive finite number`)
}
if (!dest || !path.isAbsolute(dest)) {
throw new Error(`Invalid dest path: ${dest}`)
}
// Use one canonical lexical representation for filesystem operations and
// archive-boundary checks. path.resolve also removes a trailing separator.
dest = path.resolve(dest)
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true })
} else {
let stat = fs.statSync(dest)
if (stat && !stat.isDirectory()) {
throw new Error(`${dest} exists, but not directory!`)
}
}
let mod = getRequestModule(url)
let opts = resolveRequestOptions(url, options)
if (!opts.agent && options.agent) opts.agent = options.agent
let extname = path.extname(url.pathname)
return new Promise<string>((resolve, reject) => {
let timer: NodeJS.Timeout
let settled = false
let cancellation: { dispose(): void } | undefined
const cleanup = (): void => {
cancellation?.dispose()
cancellation = undefined
if (timer) clearTimeout(timer)
}
const succeed = (value: string): void => {
if (settled) return
settled = trueView on GitHub (pinned to 50e974d969)
Solutions
- Pass the parent directory as dest, not a file path: dest: '/opt/app' instead of '/opt/app/pkg.zip'.
- Remove or rename the conflicting file at dest, or pick a new directory.
- Check before calling: fs.statSync(dest).isDirectory() and clean up if false.
Example fix
// before
download({ url, dest: '/opt/app/tool.zip' }) // existing file
// after
download({ url, dest: '/opt/app' }) // dest is a directory Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs')
function assertExistingDirectory(p) {
if (fs.existsSync(p) && !fs.statSync(p).isDirectory())
throw new Error(`${p} exists but is not a directory`)
return p
} Type guard
function isDirectory(p: string): boolean { try { return fs.statSync(p).isDirectory() } catch { return false } } Try / catch
try {
await download({ url, dest })
} catch (e) {
if (e.message.endsWith('exists, but not directory!')) {
fs.rmSync(dest, { force: true }) // only if safe to delete
return download({ url, dest })
}
throw e
} Prevention
- Remember dest is a DIRECTORY, not the output filename.
- fs.mkdirSync(dest, { recursive: true }) before downloading to guarantee a directory.
- Check fs.statSync(dest).isDirectory() when reusing paths from config.
When it happens
Trigger: Calling download({ url, dest: '/opt/app/pkg.zip' }) where /opt/app/pkg.zip is an existing file, because dest was mistaken for the output file path rather than the output directory; a stale file occupies the intended directory name.
Common situations: Confusing dest (directory) with an output filepath; previous runs created a file with the same name as the intended directory; mount points or broken directories left by deployment tooling.
Related errors
- Invalid dest path: ${dest}
- ${name} must be a positive finite number
- Directory ${folder} not exists
- name and doComplete required for createSource
- Feature param could only starts with nvim and patch
AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31).
Data as JSON: /api/errors/d4ef1e278a2719e8.
Report an issue: GitHub.