neoclide/coc.nvim · error
Invalid extraction directory: ${current}
Error message
Invalid extraction directory: ${current} What it means
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.
Source
Thrown at src/model/download.ts:150
let input = await openZipEntry(zipfile, entry)
await writeZipEntry(input, target)
}
zipfile.readEntry()
}, fail).catch(fail)
})
zipfile.readEntry()
})
}
async function ensureNoSymlink(dest: string, target: string): Promise<void> {
let relative = path.relative(dest, target)
let current = dest
for (let part of relative.split(path.sep).filter(Boolean)) {
current = path.join(current, part)
try {
let stat = await fs.promises.lstat(current)
if (stat.isSymbolicLink()) throw new Error(`Refusing to extract through symbolic link: ${current}`)
if (!stat.isDirectory()) throw new Error(`Invalid extraction directory: ${current}`)
} catch (e) {
if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e
}
}
}
async function writeZipEntry(input: NodeJS.ReadableStream, target: string): Promise<void> {
let flags = fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC
if (typeof fs.constants.O_NOFOLLOW === 'number') flags |= fs.constants.O_NOFOLLOW
let handle = await fs.promises.open(target, flags, 0o666)
try {
await pipeline(input, handle.createWriteStream())
} finally {
await handle.close().catch(() => undefined)
}
}
/**View on GitHub (pinned to 50e974d969)
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.
Example fix
// before
// dest contains file 'lib', archive contains 'lib/foo.js'
download({ url, dest: '/opt/app', extract: true })
// after
fs.rmSync('/opt/app/lib'); // or rename it
download({ url, dest: '/opt/app', extract: true }) Defensive patterns
Strategy: validation
Validate before calling
const fs = require('fs'), path = require('path')
function destIsCleanExtractionRoot(dest) {
if (!fs.existsSync(dest)) return true
return fs.statSync(dest).isDirectory()
// ideally also list archive entry dirs and ensure none collide with files in dest
} Try / catch
try {
await download({ url, dest, extract: true })
} catch (e) {
if (String(e.message).startsWith('Invalid extraction directory:')) {
const bad = e.message.split(': ')[1]
fs.rmSync(bad, { force: true }) // after confirming it is safe to remove
return retryExtraction()
}
throw e
} Prevention
- 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.
When it happens
Trigger: 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'.
Common situations: 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.
Related errors
- unable to watch
- ${e instanceof Error ? e.message : String(e)}
- Refusing to extract through symbolic link: ${current}
- Invalid dest path: ${dest}
- ${dest} exists, but not directory!
AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31).
Data as JSON: /api/errors/7dab52e9d5680743.
Report an issue: GitHub.