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
- Inspect `error.cause` — it carries the original `EACCES`/`EPERM`/access-denied error with the exact path.
- 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.
- Ensure the screenshot directory (default `__screenshots__/` next to the test file) is inside Vite's `server.fs.allow` roots.
- 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
- Keep the screenshot directory inside the project root.
- If the API is network-exposed, explicitly set `api.allowWrite: true` (and understand the risk).
- Add screenshot target dirs to `server.fs.allow` when customizing paths.
- Always read `err.cause` — the real OS error is nested there.
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
- Access denied to "${path}". See Vite config documentation fo
- Access denied to "${path}". See Vite config documentation fo
- Cannot modify file "${path}". File writing is disabled becau
- Cannot take a screenshot without a test path
- Cannot take a screenshot outside of a test.
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/abec29187310c33f.json.
Report an issue: GitHub.