{"id":"abec29187310c33f","repo":"vitest-dev/vitest","slug":"couldn-t-write-file-to-fs","errorCode":null,"errorMessage":"Couldn't write file to fs","messagePattern":"Couldn't write file to fs","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/browser/src/node/commands/screenshotMatcher/index.ts","lineNumber":571,"sourceCode":"    target,\n  })\n\n  return {\n    buffer: screenshot,\n    image: await codec.decode(screenshot, {}),\n  }\n}\n\n/** Writes encoded images to disk, creating parent directories as needed. */\nasync function writeScreenshot(path: string, image: TypedArray, project: TestProject) {\n  try {\n    assertBrowserApiWrite(project, path)\n    assertBrowserFileAccess(project, path)\n    await mkdir(dirname(path), { recursive: true })\n    await writeFile(path, image)\n  }\n  catch (cause) {\n    throw new Error('Couldn\\'t write file to fs', { cause })\n  }\n}\n","sourceCodeStart":553,"sourceCodeEnd":574,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/browser/src/node/commands/screenshotMatcher/index.ts#L553-L574","documentation":"`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`.","triggerScenarios":"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).","commonSituations":"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`.","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."],"exampleFix":"// before — custom screenshot dir outside workspace\nexpect(page).toMatchScreenshot('a', { screenshotDirectory: '/tmp/out' })\n\n// after — keep it inside the workspace root\nexpect(page).toMatchScreenshot('a', { screenshotDirectory: '__screenshots__' })","handlingStrategy":"try-catch","validationCode":"import { existsSync, accessSync, constants } from 'node:fs'\nimport { dirname } from 'pathe'\n\nfunction canWriteTo(path: string): boolean {\n  try {\n    const dir = dirname(path)\n    if (!existsSync(dir)) return true // will be mkdir'd\n    accessSync(dir, constants.W_OK)\n    return true\n  } catch {\n    return false\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await expect(page).toMatchScreenshot()\n} catch (err) {\n  if (err instanceof Error && /Couldn't write file to fs/.test(err.message)) {\n    console.error('Screenshot write failed:', err.cause)\n    // e.g. check api.allowWrite, server.fs.allow, disk\n  }\n  throw err\n}","preventionTips":["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."],"tags":["filesystem","screenshot","permissions","security"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}