Skip to content
Latchkey

Node "ENOENT no such file or directory, open" in CI - Fix the Missing File

ENOENT on open means your code tried to read a file at a path that does not exist on the CI runner, frequently because of a relative path resolved from the wrong cwd.

What this error means

A node process throws Error: ENOENT: no such file or directory, open '<path>' when reading config, fixtures, or assets. It works locally but the file is missing in CI.

node
Error: ENOENT: no such file or directory, open './config/settings.json'
    at Object.openSync (node:fs:603:3)
    at readFileSync (node:fs:471:35) {
  errno: -2, code: 'ENOENT', syscall: 'open'
}

Diagnose it: the shell in CI is not your shell

Package scripts run under a different shell, a different PATH, and a non-interactive environment on a runner. Most scripts that fail only in CI are relying on something the login shell gave them locally: a tool on PATH, an environment variable from a dotfile, or a TTY.

Terminal
# what the script can actually see
npm run env | grep -E "^(PATH|NODE_ENV|CI)=" 

# is the binary on PATH for the script, not just for you?
npm exec -- which <tool> || echo "not resolvable from npm scripts"

# run the exact script with tracing
sh -x -c "$(node -p "require('./package.json').scripts.build")"

Common causes

A relative path resolved from the wrong working directory

fs resolves relative paths against process.cwd(), which in CI may differ from where the file actually lives.

The file is gitignored or generated

The file exists locally but is not committed or not produced in CI, so it is absent on a clean checkout.

How to fix it

Resolve paths relative to the module

  1. Build an absolute path from the module location instead of relying on cwd.
  2. Use that absolute path for the read.
JavaScript
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const here = dirname(fileURLToPath(import.meta.url));
const file = join(here, 'config', 'settings.json');

Make sure the file exists in CI

  1. Confirm the file is committed or generated earlier in the job.
  2. Add the generation step before the read if it is produced.

Make failures fail the job

A multi-command script can report success while a middle command failed, which produces the worst kind of CI result: a green build that shipped something broken.

.github/workflows/ci.yml
# pipefail is NOT set by default in every runner shell
- name: Build
  shell: bash
  run: |
    set -euo pipefail
    npm run build | tee build.log

How to prevent it

  • Resolve runtime file paths from import.meta.url or __dirname, commit required fixtures, and avoid depending on the CI working directory matching your local shell.

Frequently asked questions

What causes Node "ENOENT no such file or directory, open" in CI?
There are 2 common causes: a relative path resolved from the wrong working directory and the file is gitignored or generated. fs resolves relative paths against process.cwd(), which in CI may differ from where the file actually lives.
How do I fix Node "ENOENT no such file or directory, open" in CI?
There are 2 fixes depending on which cause you have: resolve paths relative to the module and make sure the file exists in ci. Work through them in order, since the first is the most common.
What does Node "ENOENT no such file or directory, open" in CI actually mean?
A node process throws Error: ENOENT: no such file or directory, open '<path>' when reading config, fixtures, or assets.
How do I stop Node "ENOENT no such file or directory, open" in CI happening again?
Resolve runtime file paths from import.meta.url or __dirname, commit required fixtures, and avoid depending on the CI working directory matching your local shell.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card