vitest-dev/vitest · error · Error
Failed to create Vitest API token at
Error message
Failed to create Vitest API token at ${tokenPaths.join(' or ')} What it means
`resolveApiToken` tries two token locations — `$XDG_DATA_HOME/vitest/.vitest-secret-token` (or platform equivalent) and `<workspace>/node_modules/.vitest/.vitest-secret-token`. Each is created via `resolveTokenFromPath`, which reads an existing token or generates a UUID and writes it with restrictive perms. If both attempts throw (typically FS permission or quota errors), the function gives up and reports both candidate paths.
Solutions
- Ensure `$HOME` (or `$XDG_DATA_HOME`) is writable, or run in a workspace whose `node_modules` is writable.
- Mount a writable volume for the user data dir in containers: `-v vitest-data:/home/node/.local/share`.
- Pre-create the token file with `0600` perms containing a UUID so Vitest only needs read access.
- If sandboxed, disable the API/UI feature that requires the token, or run Vitest with the appropriate `--allow-fs`/sandbox settings.
Example fix
# before — read-only HOME and node_modules in CI docker run --read-only myimage npx vitest --ui # after — writable data dir mkdir -p vitest-data docker run -v "$PWD/vitest-data:/home/node/.local/share/vitest" myimage npx vitest --ui
Defensive patterns
Strategy: try-catch
Validate before calling
import { existsSync, mkdirSync } from 'node:fs'
import { join } from 'node:path'
function ensureWritableTokenDir(root: string): string[] {
const candidates = [
join(process.env.XDG_DATA_HOME || `${process.env.HOME}/.local/share`, 'vitest'),
join(root, 'node_modules/.vitest'),
]
for (const dir of candidates) {
try {
mkdirSync(dir, { recursive: true, mode: 0o700 })
return [dir]
} catch {}
}
throw new Error('No writable token directory; check HOME/node_modules perms')
} Try / catch
try {
resolveApiToken(workspaceRoot)
} catch (e) {
if (/Failed to create Vitest API token/.test((e as Error).message)) {
// surface a friendlier message + guidance, then exit or disable UI mode
console.error('Vitest needs a writable token dir. Set $XDG_DATA_HOME to a writable path.')
process.exit(1)
}
throw e
} Prevention
- Ensure `$HOME` or `$XDG_DATA_HOME` is writable in CI containers.
- Avoid `--read-only` root filesystems when using `--ui`/browser mode.
- Pre-create the token file with `0600` perms to require only read access.
When it happens
Trigger: Read-only home directory AND read-only/wrapped `node_modules` (pnnpm virtual store, immutable container, `npm pack` tarball); disk full or quota exceeded when writing the token; SELinux/AppArmor denying `mkdir`/`writeFile` at both locations; running in a sandbox (browser/worker) where `fs` is shimmed and throws.
Common situations: CI images mounted read-only; Docker containers running as a uid without write access to `$HOME`; `--read-only` filesystem flags; corporate-locked home directories; Vitest UI/browser mode where the API token gates the WebSocket.
Related errors
- Access denied to " ". See Vite config documentation for…
- Access denied to " ". See Vite config documentation for…
- Cannot find environment for
- Cannot merge reports with --watch enabled
- cannot read when saving inline snapshot
AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11).
Data as JSON: /api/errors/f490825ac0a62b4e.
Report an issue: GitHub.
Appendix: source
Thrown at packages/vitest/src/node/config/apiToken.ts:56
}
catch {}
return { token, tokenCreated: true }
}
export function resolveApiToken(root: string): { token: string; tokenCreated: boolean; tokenPath: string } {
const tokenPaths = [
join(getUserDataDir(), 'vitest', API_TOKEN_FILE),
join(searchForWorkspaceRoot(root), 'node_modules/.vitest', API_TOKEN_FILE),
]
for (const tokenPath of tokenPaths) {
try {
return { ...resolveTokenFromPath(tokenPath), tokenPath }
}
catch {}
}
throw new Error(`Failed to create Vitest API token at ${tokenPaths.join(' or ')}`)
}
View on GitHub (pinned to 1fa9837ec2)