{"record":{"id":"d4ef1e278a2719e8","repo":"neoclide/coc.nvim","slug":"dest-exists-but-not-directory","errorCode":null,"errorMessage":"${dest} exists, but not directory!","messagePattern":"(.+?) exists, but not directory!","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/model/download.ts","lineNumber":231,"sourceCode":"  for (let [name, value] of Object.entries({\n    maxDownloadSize: options.maxDownloadSize,\n    maxExtractSize: options.maxExtractSize,\n    maxArchiveEntries: options.maxArchiveEntries\n  })) {\n    if (value !== undefined && (!Number.isFinite(value) || value <= 0)) throw new Error(`${name} must be a positive finite number`)\n  }\n  if (!dest || !path.isAbsolute(dest)) {\n    throw new Error(`Invalid dest path: ${dest}`)\n  }\n  // Use one canonical lexical representation for filesystem operations and\n  // archive-boundary checks. path.resolve also removes a trailing separator.\n  dest = path.resolve(dest)\n  if (!fs.existsSync(dest)) {\n    fs.mkdirSync(dest, { recursive: true })\n  } else {\n    let stat = fs.statSync(dest)\n    if (stat && !stat.isDirectory()) {\n      throw new Error(`${dest} exists, but not directory!`)\n    }\n  }\n  let mod = getRequestModule(url)\n  let opts = resolveRequestOptions(url, options)\n  if (!opts.agent && options.agent) opts.agent = options.agent\n  let extname = path.extname(url.pathname)\n  return new Promise<string>((resolve, reject) => {\n    let timer: NodeJS.Timeout\n    let settled = false\n    let cancellation: { dispose(): void } | undefined\n    const cleanup = (): void => {\n      cancellation?.dispose()\n      cancellation = undefined\n      if (timer) clearTimeout(timer)\n    }\n    const succeed = (value: string): void => {\n      if (settled) return\n      settled = true","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/neoclide/coc.nvim/blob/50e974d9692461a69147d5cab146a8d3e439abe4/src/model/download.ts#L213-L249","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ndownload({ url, dest: '/opt/app/tool.zip' }) // existing file\n// after\ndownload({ url, dest: '/opt/app' }) // dest is a directory","handlingStrategy":"validation","validationCode":"const fs = require('fs')\nfunction assertExistingDirectory(p) {\n  if (fs.existsSync(p) && !fs.statSync(p).isDirectory())\n    throw new Error(`${p} exists but is not a directory`)\n  return p\n}","typeGuard":"function isDirectory(p: string): boolean { try { return fs.statSync(p).isDirectory() } catch { return false } }","tryCatchPattern":"try {\n  await download({ url, dest })\n} catch (e) {\n  if (e.message.endsWith('exists, but not directory!')) {\n    fs.rmSync(dest, { force: true }) // only if safe to delete\n    return download({ url, dest })\n  }\n  throw e\n}","preventionTips":["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."],"tags":["filesystem","download","path","validation"],"backgroundTag":"destination-not-a-directory","analyzedSha":"50e974d9692461a69147d5cab146a8d3e439abe4","analyzedAt":"2026-08-31T11:17:23.966Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}