-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
EPERM errors when running coverage in sandboxed worker environments
Description
When running vitest with coverage in sandboxed worker environments that restrict parent directory access, EPERM (operation not permitted) errors occur during module resolution. This happens when vitest attempts to walk up the directory tree to access parent directory files like package.json or node_modules during coverage initialization.
Environment
- Vitest version: (latest as of January 2026)
- Node version: (varies by environment)
- Operating System: macOS/Linux (sandboxed worker environments)
- Coverage provider: v8
Steps to Reproduce
- Create a sandboxed environment that restricts access to parent directories (e.g., using macOS sandboxing, Docker with limited mount points, or similar isolation mechanisms)
- Set up a vitest project with coverage configuration:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
root: '.',
test: {
globals: true,
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
reportsDirectory: './coverage',
},
},
});- Run
vitest --coveragefrom within the sandboxed environment - Observe EPERM errors when vitest tries to access files outside the sandboxed directory
Expected Behavior
Vitest coverage should run successfully without needing to access parent directory files, or should have a configuration option to restrict module resolution to the project root.
Actual Behavior
EPERM errors occur when vitest's module resolution tries to access files in parent directories:
- Reading
package.jsonfiles up the directory tree - Accessing
node_modulesin parent directories - Walking up to find workspace roots or configuration files
Current Workaround
I've created a wrapper script that patches fs.readFileSync to gracefully handle EPERM errors by redirecting to local equivalents:
// vitest-coverage-wrapper.mjs
import fs from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join, basename } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Store the original readFileSync
const originalReadFileSync = fs.readFileSync;
// Patch fs.readFileSync to handle EPERM errors gracefully
fs.readFileSync = function(...args) {
try {
return originalReadFileSync.apply(this, args);
} catch (error) {
if (error.code === 'EPERM' && error.path) {
const filename = basename(error.path);
// For package.json files, try our local directory first
if (filename === 'package.json') {
try {
const localPath = join(__dirname, 'package.json');
return originalReadFileSync(localPath, args[1]);
} catch (localError) {
throw error;
}
}
// For node_modules paths, try local node_modules
if (error.path.includes('node_modules/')) {
const nodeModulesIndex = error.path.lastIndexOf('node_modules/');
const relativePath = error.path.substring(nodeModulesIndex);
const localPath = join(__dirname, relativePath);
try {
return originalReadFileSync(localPath, args[1]);
} catch (localError) {
if (filename === 'package.json') {
return JSON.stringify({ name: "fallback", version: "1.0.0", type: "module" });
}
throw error;
}
}
}
throw error;
}
};
// Now load and run vitest
import('vitest/dist/cli.js');And updated package.json:
{
"scripts": {
"test:coverage": "node vitest-coverage-wrapper.mjs --coverage"
}
}Proposed Solutions
This could be improved upstream by:
-
Adding a configuration option to restrict module resolution to the project root:
coverage: { restrictToProjectRoot: true, // Don't walk up parent directories // or maxParentDepth: 0, // Limit how far up the tree to search }
-
Using try-catch blocks around parent directory access in vitest's module resolution code to gracefully handle permission errors
-
Respecting the
rootconfiguration more strictly when searching for package.json and node_modules, avoiding unnecessary parent directory traversal
Related Issues
This is related to other directory access and module resolution issues:
- EPERM: operation not permitted, scandir '/Library/Application Support/com.apple.TCC' #2666 (EPERM: operation not permitted, scandir)
- Root option ignored if vite config also has root option #2050 (Root option ignored)
- Directory Provided by
-rFlag Not Used as Coverage Root #1902 (Directory provided by -r flag not used as coverage root) - Module resolution bug in projects with --changed flag #8654 (Module resolution bug with --changed flag)
Impact
This affects any environment that uses sandboxing or restricted filesystem access, including:
- CI/CD pipelines with sandboxed workers
- Docker containers with limited mount points
- macOS sandboxed applications
- Isolated development environments
The workaround requires patching Node.js filesystem APIs, which is not ideal and could have unintended side effects.