tauri-apps/tauri · error · Error
'${path}' is not a directory.
Error message
'${path}' is not a directory. What it means
The @tauri-apps/api build script (packages/api/rollup.config.ts) calls cleanDir() on packages/api/dist before rollup starts. cleanDir() opens the path with fs.opendirSync(). If the operating system returns ENOTDIR, the path exists but is not a directory (usually a regular file named dist). The script then throws this error. A missing directory (ENOENT) is ignored, so the error proves that something occupies the dist path as a non-directory.
Source
Thrown at packages/api/rollup.config.ts:104
writeBundle() {
// copy necessary files like `CHANGELOG.md` , `README.md` and Licenses to `./dist`
fg.sync('(LICENSE*|*.md|package.json)').forEach((f) =>
copyFileSync(f, `dist/${f}`)
)
}
}
}
function cleanDir(path: string) {
let dir: Dir
try {
dir = opendirSync(path)
} catch (err: any) {
switch (err.code) {
case 'ENOENT':
return // Noop when directory don't exists.
case 'ENOTDIR':
throw new Error(`'${path}' is not a directory.`)
default:
throw err
}
}
let file = dir.readSync()
while (file) {
const filePath = join(path, file.name)
rmSync(filePath, { recursive: true })
file = dir.readSync()
}
dir.closeSync()
}
View on GitHub (pinned to 2f1cd75b0f)
Solutions
- Remove the offending path: rm -f packages/api/dist (or delete the symlink), then rerun the build.
- Inspect what dist is: ls -l packages/api/dist. If it is a symlink to a file, delete it or point it to a directory.
- Find the writer: search CI steps and npm scripts for commands that write to ./dist as a file, and fix them.
- For CI, run the build from a clean checkout (git clean -fdx) so stale paths cannot survive.
Example fix
// before: packages/api/dist is a regular file $ ls -l packages/api/dist -rw-r--r-- 1 user 0 dist $ rollup -c // Error: '/repo/packages/api/dist' is not a directory. // after $ rm -f packages/api/dist $ rollup -c // cleanDir no-ops on ENOENT, then builds fresh
Defensive patterns
Strategy: validation
Validate before calling
import { statSync, rmSync } from 'fs'
import { join } from 'path'
// run before cleanDir(join(__dirname, './dist'))
function ensureCleanableDir(path: string) {
let st
try {
st = statSync(path) // follows symlinks
} catch {
return // path absent: cleanDir no-ops on ENOENT
}
if (!st.isDirectory()) {
rmSync(path, { force: true }) // remove the offending file/symlink
}
} Try / catch
try {
cleanDir(dist)
} catch (err) {
if (/is not a directory\./.test(err.message)) {
rmSync(dist, { force: true }) // drop the non-directory
cleanDir(dist) // retry once
} else {
throw err // EACCES and other fs errors: surface as-is
}
} Prevention
- Treat packages/api/dist as build output only; never redirect a file to that path in scripts or CI.
- Run builds from a clean checkout (git clean -fdx) in CI so stale paths cannot survive.
- If dist must be a symlink, point it to a directory, never to a file.
- Audit npm scripts for commands that write to ./dist with a shell redirect.
When it happens
Trigger: Run the packages/api build (rollup -c / yarn build) while packages/api/dist exists as a regular file or as a symlink to a file. opendirSync(dist) fails with ENOTDIR and cleanDir throws at rollup.config.ts:104. Permission errors (EACCES, EPERM) take the default branch and rethrow the raw fs error instead.
Common situations: A CI step or shell redirect created a file named dist (for example 'curl -o dist ...' or '> dist'). A symlink points dist to a file. A previous artifact copy replaced the directory. The build runs in a dirty working tree after such an operation.
Related errors
AI-assisted analysis of tauri-apps/tauri@2f1cd75b0f (2026-08-16).
Data as JSON: /api/errors/5fba48b714c29d05.
Report an issue: GitHub.