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

  1. Remove the offending path: rm -f packages/api/dist (or delete the symlink), then rerun the build.
  2. 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.
  3. Find the writer: search CI steps and npm scripts for commands that write to ./dist as a file, and fix them.
  4. 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

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.