neoclide/coc.nvim · error

Invalid dest path: ${dest}

Error message

Invalid dest path: ${dest}

What it means

download() requires the dest option to be a non-empty absolute filesystem path, because the resolved destination is used directly for fs operations and archive-boundary security checks. An empty string, undefined, or a relative path throws 'Invalid dest path: <value>'.

Source

Thrown at src/model/download.ts:221

/**
 * Download file from url, with optional untar/unzip support.
 * @param {string} url
 * @param {DownloadOptions} options contains dest folder and optional onProgress callback
 */
export default function download(urlInput: string | URL, options: DownloadOptions, token?: CancellationToken, obj: any = {}): Promise<string> {
  let url = toURL(urlInput)
  let { etagAlgorithm } = options
  let { dest, onProgress, extract } = options
  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

View on GitHub (pinned to 50e974d969)

Solutions

  1. Pass an absolute path: path.join(os.homedir(), 'downloads', 'pkg.zip') or path.resolve('downloads').
  2. Replace '~' manually via os.homedir() since Node does not expand it.
  3. Validate before calling: if (!dest || !path.isAbsolute(dest)) throw new Error(...).

Example fix

// before
download({ url, dest: '~/downloads/pkg.zip' })
// after
import os from 'os'
download({ url, dest: path.join(os.homedir(), 'downloads', 'pkg.zip') })
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path'), os = require('os')
function assertAbsDir(p) {
  if (!p || !path.isAbsolute(p)) throw new Error(`dest must be an absolute path, got: ${p}`)
  return p
}
const dest = assertAbsDir(path.join(os.homedir(), 'downloads'))

Type guard

function isAbsolutePath(p: unknown): p is string { return typeof p === 'string' && p.length > 0 && path.isAbsolute(p) }

Try / catch

try {
  await download({ url, dest })
} catch (e) {
  if (/Invalid dest path/.test(e.message)) {
    return download({ url, dest: path.resolve(dest || '.') })
  }
  throw e
}

Prevention

When it happens

Trigger: Calling download({ url }) without dest; passing a relative path like 'downloads/pkg.zip' or '~/pkg.zip'; passing dest: '' from an unset config value.

Common situations: Using '~' expecting shell-style expansion (Node does not expand it); building dest by string concatenation where an earlier segment was empty; running from different working directories so a previously-working relative path is now relative to the wrong cwd.

Related errors


AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31). Data as JSON: /api/errors/64fce1bd840ed9ac. Report an issue: GitHub.