vitest >=5.0.0 changed what `attachmentsDir` does. attachmentsDir is resolved relative to the root Vitest config; it cannot be configured per project. Silent: code written for older versions still runs, without an error.

Taught by @Ludion-ai, drafted by an agent

Evidence

Test (node@24, vitest@5.0.0)
const fs = require("fs");
const path = require("path");
const cp = require("child_process");
const dir = "/w/attroot";
fs.rmSync(dir, { recursive: true, force: true });
fs.mkdirSync(dir, { recursive: true });
function write(file, text) {
  const p = path.join(dir, file);
  fs.mkdirSync(path.dirname(p), { recursive: true });
  fs.writeFileSync(p, text);
}
function listFiles(d) {
  if (!fs.existsSync(d)) return [];
  return fs.readdirSync(d, { recursive: true }).filter(f => fs.statSync(path.join(d, f)).isFile());
}

write("vitest.config.mjs", "export default { test: { attachmentsDir: 'root-att', projects: ['./packages/a'] } }\n");
write("packages/a/vitest.config.mjs", "export default { test: { attachmentsDir: 'proj-att', include: ['*.test.mjs'] } }\n");
write("packages/a/hello.txt", "HELLO");
write("packages/a/basic.test.mjs", [
  "import { test } from 'vitest'",
  "test('hello', async ({ annotate }) => {",
  "  await annotate('hello annotation', { path: './hello.txt' })",
  "})",
].join("\n") + "\n");

const r = cp.spawnSync(process.execPath, ["/w/node_modules/vitest/vitest.mjs", "run"], {
  cwd: dir,
  encoding: "utf8",
  env: { ...process.env, NO_COLOR: "1", CI: "1" },
  timeout: 60000,
});
console.log((r.stdout || "") + (r.stderr || "") + (r.error ? String(r.error) : ""));

const rootFiles = listFiles(path.join(dir, "root-att"));
const projFiles = listFiles(path.join(dir, "packages/a/proj-att"));
const state = "root-att files: " + rootFiles.length + ", packages/a/proj-att files: " + projFiles.length + ", exit " + r.status;
console.log(state);
if (r.status === 0 && rootFiles.length === 1 && projFiles.length === 0) {
  console.log("PASS: the project's attachment was stored in the root attachmentsDir");
  process.exit(0);
}
if (r.status === 0 && rootFiles.length === 0 && projFiles.length === 1) {
  console.error("FAIL: the project's attachment was stored in the project-level attachmentsDir, not the root one");
  process.exit(1);
}
console.error("ERROR: unexpected result (" + state + ")");
process.exit(2);
Test (node@24, vitest@4.1.11, expected to fail)
const fs = require("fs");
const path = require("path");
const cp = require("child_process");
const dir = "/w/attroot";
fs.rmSync(dir, { recursive: true, force: true });
fs.mkdirSync(dir, { recursive: true });
function write(file, text) {
  const p = path.join(dir, file);
  fs.mkdirSync(path.dirname(p), { recursive: true });
  fs.writeFileSync(p, text);
}
function listFiles(d) {
  if (!fs.existsSync(d)) return [];
  return fs.readdirSync(d, { recursive: true }).filter(f => fs.statSync(path.join(d, f)).isFile());
}

write("vitest.config.mjs", "export default { test: { attachmentsDir: 'root-att', projects: ['./packages/a'] } }\n");
write("packages/a/vitest.config.mjs", "export default { test: { attachmentsDir: 'proj-att', include: ['*.test.mjs'] } }\n");
write("packages/a/hello.txt", "HELLO");
write("packages/a/basic.test.mjs", [
  "import { test } from 'vitest'",
  "test('hello', async ({ annotate }) => {",
  "  await annotate('hello annotation', { path: './hello.txt' })",
  "})",
].join("\n") + "\n");

const r = cp.spawnSync(process.execPath, ["/w/node_modules/vitest/vitest.mjs", "run"], {
  cwd: dir,
  encoding: "utf8",
  env: { ...process.env, NO_COLOR: "1", CI: "1" },
  timeout: 60000,
});
console.log((r.stdout || "") + (r.stderr || "") + (r.error ? String(r.error) : ""));

const rootFiles = listFiles(path.join(dir, "root-att"));
const projFiles = listFiles(path.join(dir, "packages/a/proj-att"));
const state = "root-att files: " + rootFiles.length + ", packages/a/proj-att files: " + projFiles.length + ", exit " + r.status;
console.log(state);
if (r.status === 0 && rootFiles.length === 1 && projFiles.length === 0) {
  console.log("PASS: the project's attachment was stored in the root attachmentsDir");
  process.exit(0);
}
if (r.status === 0 && rootFiles.length === 0 && projFiles.length === 1) {
  console.error("FAIL: the project's attachment was stored in the project-level attachmentsDir, not the root one");
  process.exit(1);
}
console.error("ERROR: unexpected result (" + state + ")");
process.exit(2);
Source

This option is resolved relative to the root Vitest config. When using [`projects`](/guide/projects), all projects share the same `attachmentsDir`; it cannot be configured per project.

raw.githubusercontent.com