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

  1. Ensure `$HOME` (or `$XDG_DATA_HOME`) is writable, or run in a workspace whose `node_modules` is writable.
  2. Mount a writable volume for the user data dir in containers: `-v vitest-data:/home/node/.local/share`.
  3. Pre-create the token file with `0600` perms containing a UUID so Vitest only needs read access.
  4. 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

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


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)