Skip to content

EPERM errors when running coverage in sandboxed worker environments #9386

@aduros

Description

@aduros

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

  1. Create a sandboxed environment that restricts access to parent directories (e.g., using macOS sandboxing, Docker with limited mount points, or similar isolation mechanisms)
  2. 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',
    },
  },
});
  1. Run vitest --coverage from within the sandboxed environment
  2. 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.json files up the directory tree
  • Accessing node_modules in 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:

  1. 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
    }
  2. Using try-catch blocks around parent directory access in vitest's module resolution code to gracefully handle permission errors

  3. Respecting the root configuration 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:

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions