{"record":{"id":"7dab52e9d5680743","repo":"neoclide/coc.nvim","slug":"invalid-extraction-directory-current","errorCode":null,"errorMessage":"Invalid extraction directory: ${current}","messagePattern":"Invalid extraction directory: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/model/download.ts","lineNumber":150,"sourceCode":"          let input = await openZipEntry(zipfile, entry)\n          await writeZipEntry(input, target)\n        }\n        zipfile.readEntry()\n      }, fail).catch(fail)\n    })\n    zipfile.readEntry()\n  })\n}\n\nasync function ensureNoSymlink(dest: string, target: string): Promise<void> {\n  let relative = path.relative(dest, target)\n  let current = dest\n  for (let part of relative.split(path.sep).filter(Boolean)) {\n    current = path.join(current, part)\n    try {\n      let stat = await fs.promises.lstat(current)\n      if (stat.isSymbolicLink()) throw new Error(`Refusing to extract through symbolic link: ${current}`)\n      if (!stat.isDirectory()) throw new Error(`Invalid extraction directory: ${current}`)\n    } catch (e) {\n      if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e\n    }\n  }\n}\n\nasync function writeZipEntry(input: NodeJS.ReadableStream, target: string): Promise<void> {\n  let flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC\n  if (typeof fs.constants.O_NOFOLLOW === 'number') flags |= fs.constants.O_NOFOLLOW\n  let handle = await fs.promises.open(target, flags, 0o666)\n  try {\n    await pipeline(input, handle.createWriteStream())\n  } finally {\n    await handle.close().catch(() => undefined)\n  }\n}\n\n/**","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/neoclide/coc.nvim/blob/50e974d9692461a69147d5cab146a8d3e439abe4/src/model/download.ts#L132-L168","documentation":"ensureNoSymlink walks each intermediate directory component of the extraction path and lstats it. If a component exists but is a regular file (or anything not a directory), the archive cannot contain children beneath it, so extraction aborts with 'Invalid extraction directory: <path>'. This prevents archives from overwriting or tunneling through non-directory entries.","triggerScenarios":"Extracting an archive whose entry path passes through a component that already exists on disk as a plain file, e.g. dest contains a file 'build' and the archive has entry 'build/output.js'.","commonSituations":"Re-extracting into a directory previously partially populated; a leftover file from an earlier failed extraction; name collisions where an archive directory name matches an existing file.","solutions":["Remove or rename the conflicting non-directory file at the reported path, then retry the extraction.","Use a clean, empty destination directory for the extraction.","Compare archive entry names against existing dest contents and resolve collisions before extracting."],"exampleFix":"// before\n// dest contains file 'lib', archive contains 'lib/foo.js'\ndownload({ url, dest: '/opt/app', extract: true })\n// after\nfs.rmSync('/opt/app/lib'); // or rename it\ndownload({ url, dest: '/opt/app', extract: true })","handlingStrategy":"validation","validationCode":"const fs = require('fs'), path = require('path')\nfunction destIsCleanExtractionRoot(dest) {\n  if (!fs.existsSync(dest)) return true\n  return fs.statSync(dest).isDirectory()\n  // ideally also list archive entry dirs and ensure none collide with files in dest\n}","typeGuard":null,"tryCatchPattern":"try {\n  await download({ url, dest, extract: true })\n} catch (e) {\n  if (String(e.message).startsWith('Invalid extraction directory:')) {\n    const bad = e.message.split(': ')[1]\n    fs.rmSync(bad, { force: true }) // after confirming it is safe to remove\n    return retryExtraction()\n  }\n  throw e\n}","preventionTips":["Always extract into an empty, freshly created directory.","Clean leftovers from previous failed extractions before retrying.","Compare archive entry names against existing dest contents to catch name collisions."],"tags":["filesystem","archive-extraction","path-conflict"],"backgroundTag":"extraction-path-conflict","analyzedSha":"50e974d9692461a69147d5cab146a8d3e439abe4","analyzedAt":"2026-08-31T11:17:23.966Z","schemaVersion":2},"datasetVersion":"2026-09-01T08:17:40.651Z"}