vitest-dev/vitest · error · Error

Couldn't write file to fs

Error message

Couldn't write file to fs

What it means

`writeScreenshot` wraps `mkdir -p`, `writeFile`, plus the `assertBrowserApiWrite` and `assertBrowserFileAccess` guards. Any failure (permission, disk, fs.allow boundary, or `api.allowWrite=false`) is rethrown via this generic message with the real cause attached as `Error.cause`.

Source

Thrown at packages/browser/src/node/commands/screenshotMatcher/index.ts:571

    target,
  })

  return {
    buffer: screenshot,
    image: await codec.decode(screenshot, {}),
  }
}

/** Writes encoded images to disk, creating parent directories as needed. */
async function writeScreenshot(path: string, image: TypedArray, project: TestProject) {
  try {
    assertBrowserApiWrite(project, path)
    assertBrowserFileAccess(project, path)
    await mkdir(dirname(path), { recursive: true })
    await writeFile(path, image)
  }
  catch (cause) {
    throw new Error('Couldn\'t write file to fs', { cause })
  }
}

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Inspect `error.cause` — it carries the original `EACCES`/`EPERM`/access-denied error with the exact path.
  2. If the API is exposed to the network, explicitly set `test.api.allowWrite: true` in the config (understand the risk), or bind `api.host` to localhost.
  3. Ensure the screenshot directory (default `__screenshots__/` next to the test file) is inside Vite's `server.fs.allow` roots.
  4. Verify write permissions on the target directory and that the disk isn't full.

Example fix

// before — custom screenshot dir outside workspace
expect(page).toMatchScreenshot('a', { screenshotDirectory: '/tmp/out' })

// after — keep it inside the workspace root
expect(page).toMatchScreenshot('a', { screenshotDirectory: '__screenshots__' })
Defensive patterns

Strategy: try-catch

Validate before calling

import { existsSync, accessSync, constants } from 'node:fs'
import { dirname } from 'pathe'

function canWriteTo(path: string): boolean {
  try {
    const dir = dirname(path)
    if (!existsSync(dir)) return true // will be mkdir'd
    accessSync(dir, constants.W_OK)
    return true
  } catch {
    return false
  }
}

Try / catch

try {
  await expect(page).toMatchScreenshot()
} catch (err) {
  if (err instanceof Error && /Couldn't write file to fs/.test(err.message)) {
    console.error('Screenshot write failed:', err.cause)
    // e.g. check api.allowWrite, server.fs.allow, disk
  }
  throw err
}

Prevention

When it happens

Trigger: A screenshot comparison produces a `missing-reference`, `update-reference`, or `mismatch` outcome (triggering `performSideEffects` → `writeScreenshot`), and one of: the resolved path is outside Vite's `server.fs.allow`, `api.allowWrite` is false (server exposed to network), or the OS denies the write (permissions, disk full, read-only mount).

Common situations: Running Vitest with `api.host` exposed (which defaults `allowWrite` to false); configuring a custom `screenshotDirectory` or `resolveScreenshotPath` that resolves outside the workspace root; containerized CI with read-only mounts; pnpm isolated installs where the screenshot target lives in `node_modules`.

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/abec29187310c33f.json. Report an issue: GitHub.