vitest-dev/vitest · error · Error
Couldn't write file to fs
Error message
Couldn't write file to fs
What it means
Thrown by writeScreenshot after both assertBrowserApiWrite and assertBrowserFileAccess have already passed, so it indicates a genuine filesystem failure during mkdir(dirname) or writeFile. The original error is preserved on the `cause` property. It is a defensive wrapper that normalizes any low-level FS error (EACCES, ENOSPC, EISDIR, ENOENT for an unresolvable parent) into a single human-readable message.
Solutions
- Inspect err.cause for the real POSIX error code (EACCES/ENOSPC/EISDIR) and address that specifically.
- Free disk space or raise the CI runner disk quota if err.cause.code === 'ENOSPC'.
- Change the screenshot output path to a writable location inside the project root, or grant write permission to the target directory.
- Confirm the path does not point to an existing directory or a file owned by another user.
- If running in a container, verify the mounted volume is writable by the Vitest process user.
Example fix
// before
await writeFile(path, image)
// after - surface the real cause and pick a writable, existing parent
try {
await mkdir(dirname(path), { recursive: true })
await writeFile(path, image)
}
catch (cause) {
throw new Error(`Couldn't write screenshot to ${path}: ${(cause as NodeJS.ErrnoException).code}`, { cause })
} Defensive patterns
Strategy: try-catch
Validate before calling
import { access, constants } from 'node:fs/promises'
async function canWriteTo(dir: string) {
try { await access(dir, constants.W_OK); return true } catch { return false }
} Type guard
function isNodeErrno(e: unknown): e is NodeJS.ErrnoException {
return e instanceof Error && typeof (e as NodeJS.ErrnoException).code === 'string'
} Try / catch
try { await browserCommandThatWritesScreenshot(path) }
catch (e) {
const code = (e as NodeJS.ErrnoException).cause && ((e as any).cause as NodeJS.ErrnoException).code
if (code === 'ENOSPC') /* free disk */
else if (code === 'EACCES') /* fix perms */
else throw e
} Prevention
- Run screenshot tests with a writable, project-rooted output directory.
- Surface err.cause.code in CI logs so the real POSIX error is visible.
- Assert disk headroom before writing large screenshot batches.
When it happens
Trigger: Calling a screenshot-matcher command (e.g. expect(...).toMatchScreenshot with an output path) where the destination directory cannot be created or the file cannot be written. Concrete causes: read-only filesystem, ENOSPC (disk full), EACCES on the target directory, a path that resolves to an existing directory (EISDIR), or a symbolic-link loop that mkdir cannot resolve.
Common situations: Running browser screenshot tests in a sandboxed/CI container with a read-only mount; specifying a screenshot path outside the project root that the OS refuses to create; disk-full CI runners; Windows path with illegal characters; a relative path that resolves to a directory slot already occupied.
Related errors
- Access denied to " ". See Vite config documentation for…
- Cannot compare screenshots without a test path
- Cannot take a screenshot in a concurrent test because…
- Cannot take a screenshot outside of a test.
- Cannot take a screenshot without a test path
AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11).
Data as JSON: /api/errors/abec29187310c33f.
Report an issue: GitHub.
Appendix: 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 1fa9837ec2)