TechFeedTechFeed
개발자 작업환경

ENOENT, no such file, 경로 오타 | 스크립트 cwd가 루트가 아니면?

ENOENT는 그 경로 문자열이 지금 작업 폴더 기준으로 디스크에 없을 때 난다. npm 스크립트는 package.json 위치, 크론은 홈이 cwd다. __dirname으로 고정 파일을 열고 WorkingDirectory를 넣는다. 개발자, Node.js, npm, 깃허브 액션, 도커, 크론 기준. 2026년 8월 Node.js·npm·깃허브 문서.

by

ENOENT는 코드가 틀린 게 아니라, 그 경로 문자열이 지금 작업 폴더 기준으로 디스크에 없어서 납니다. 스크립트 cwd가 저장소 루트가 아니면, 로컬에선 보이던 파일도 no such file or directory가 됩니다.


npm 스크립트는 package.json 위치에서 돌고, 크론은 홈에서 시작하는 일이 많습니다. 제가 돌리는 launchd 잡도 작업 디렉터리를 안 적으면 홈을 cwd로 잡습니다.


상대 경로를 절대 경로로 바꾸기 전에 process.cwd()와 에러에 찍힌 경로를 겹쳐 보세요. 근거는 Node.js 에러 문서npm scripts에 있습니다.


ENOENT는 경로 문자열이 비었다는 뜻이다

열린 경로가 cwd에 상대적인지부터 보면 됩니다. 노드는 fs.readFile('data/foo.json')을 프로세스 작업 폴더 아래로 풀어 엽니다. 터미널에서 프로젝트로 들어가 실행하면 있고, 홈에서 같은 명령을 치면 없습니다.


메시지에는 보통 절대 경로가 찍힙니다. 홈 아래면 작업 폴더가 홈이었다는 뜻이고, 프로젝트 아래인데도 없으면 이름 오타나 깃에 안 올라간 파일입니다.


Cannot find module과 헷갈리기 쉽습니다. 모듈 이름은 node_modules 사다리로 찾고, ENOENT는 파일 시스템 호출이 그 경로에 실체가 없을 때입니다. 패키지를 지워 깔아도 이 줄은 안 사라집니다.


혼자 사이트를 운영하면 터미널에선 프로젝트 폴더에 들어간 채로 명령을 치고, 자동 실행은 로그인도 없이 홈에서 시작합니다. 같은 파일인데 낮에는 되고 새벽에만 없다고 느껴지는 이유가 거의 여기입니다. 상대 경로를 믿기 전에, 그 명령이 어디서 시작되는지부터 적어두세요. 경로를 메모장에 옮겨 놓고 낮과 밤 로그를 나란히 보면 원인이 빨리 보입니다.


먼저 기억할 것 | 에러에 찍힌 절대 경로를 파인더나 ls로 그대로 열어 보세요. 그 위치에 파일이 없으면 cwd나 오타고, 있으면 권한·심볼릭 링크를 봅니다.


홈 폴더에서 스크립트를 실행해 상대 경로 파일을 못 찾는 개념 이미지
같은 상대 경로라도 cwd가 홈이면 프로젝트 파일을 가리키지 않는다

npm 스크립트와 크론의 cwd는 다르다

npm은 package.json 폴더를 cwd로 두고, 크론은 홈을 씁니다. 표로 나눕니다.


실행 방법기본 cwd고치는 칸
npm run foopackage.json이 있는 폴더스크립트 안 상대경로는 루트 기준
node scripts/foo.js셸의 현재 폴더cd 후 실행, 또는 __dirname
crontab / launchd대개 홈WorkingDirectory 또는 cd &&
깃허브 액션 run워크스페이스 루트defaults.run.working-directory
도커 ENTRYPOINTWORKDIRWORKDIR과 COPY 경로 일치

한국 1인 개발자가 맥 launchd로 새벽 잡을 넣으면, plist에 WorkingDirectory를 빼먹기 쉽습니다. 터미널에서 테스트할 때는 이미 프로젝트에 들어와 있어서 재현이 안 됩니다. 재현은 홈에서 같은 명령을 치는 것입니다.


액션은 받아온 저장소 루트가 작업 폴더라 로컬 자동 실행과 반대입니다. 로컬은 되고 통합 환경만 경로가 비면, 작업 폴더를 하위 앱으로 옮겼거나 리눅스 대소문자와 안 맞을 수 있습니다. 낮에 맥에서 확인했다고 밤 작업까지 같다고 보지 마세요.


홈에서 재현하고 경로를 찍기
cd ~ node /path/to/repo/scripts/foo.js # 스크립트 맨 위 console.log('cwd', process.cwd()) console.log('file', require('path').join(process.cwd(), 'data', 'foo.json'))

상대 경로 대신 파일 옆 기준으로 열기

데이터 파일은 cwd가 아니라 스크립트 파일 위치에서 엽니다. CommonJS는 __dirname, ESM은 import.meta.url을 파일 URL로 바꿔 디렉터리를 얻습니다. 이렇게 하면 홈에서 실행해도 같은 파일을 엽니다.


설정 파일은 예외가 있습니다. 사용자가 지정한 --config ./mine.json은 cwd 상대가 맞습니다. 도구가 의도적으로 작업 폴더를 쓰는 경우는 문서에 그렇게 적혀 있습니다. 기본 데이터·템플릿·SQL은 저장소 안 고정 경로가 안전합니다.


도커는 작업 폴더가 앱 루트인데 데이터만 빠지고 코드만 복사되면 런타임에 경로가 비어 있습니다. 이미지 빌드 목록에 파일이 들어갔는지, 무시 목록이 빼지 않았는지도 같이 보세요. 컨테이너 안에서 경로를 열어보는 습관이 로컬 추측보다 빠릅니다.


새벽 자동 실행은 로그인 셸이 아니라서 평소 쓰던 경로 줄임이 없습니다. 맥 작업 목록 파일에 작업 폴더를 빼먹으면 홈에서 시작합니다. 테스트할 때는 일부러 홈으로 나가 같은 명령을 쳐 보세요. 프로젝트 안에서만 확인하면 낮과 밤이 다른 버그가 남습니다.


스크립트 파일 옆의 data를 연다
const path = require('path') const fs = require('fs') const file = path.join(__dirname, '..', 'data', 'foo.json') fs.readFileSync(file, 'utf8') # ESM: import { fileURLToPath } from 'node:url' # const dir = path.dirname(fileURLToPath(import.meta.url))
  • [ ] 에러의 절대 경로를 ls로 그대로 열어 봤다
  • [ ] 홈 폴더에서 같은 명령을 재현해 봤다
  • [ ] cwd를 찍고 package.json 위치와 비교했다
  • [ ] 고정 데이터는 __dirname 또는 import.meta.url로 바꿨다
  • [ ] 크론/plist에 WorkingDirectory 또는 cd를 넣었다

스크립트 파일 위치 기준으로 데이터 파일을 여는 개념 이미지
고정 데이터는 cwd가 아니라 파일 옆 경로로 연다

오타, gitignore, 대소문자

cwd가 맞아도 파일이 없으면 세 가지입니다. 이름 오타, 깃에 안 올라간 로컬 전용 파일, 맥/리눅스 대소문자입니다. .env는 보통 gitignore라 CI에 없습니다. 시크릿은 액션 Secrets나 호스트 환경으로 넣고, 샘플은 .env.example만 커밋합니다.


한글 파일명도 갈립니다. 맥 NFD와 리눅스 NFC 정규화가 다르면 같은 글자가 다른 바이트가 됩니다. 가능하면 영문 소문자와 하이픈만 쓰세요.


권한으로 거절되면 코드가 EACCES입니다. ENOENT로 보이면 중간 디렉터리가 없는 경우가 많습니다. 부모 폴더를 먼저 만들거나, 쓰기 전에 경로가 존재하는지 확인하세요.


한글이 들어간 폴더 이름은 맥과 리눅스에서 글자 모양이 같아 보여도 저장 방식이 다를 수 있습니다. 가능하면 영문 소문자와 하이픈만 쓰고, 공백과 특수문자는 빼는 편이 안전합니다. 백업 디스크를 붙였다가 경로가 달라진 경우도 같은 줄로 보입니다.


모듈 에러와 섞지 말 것 | require('lodash') 실패는 Cannot find module입니다. readFile('./lodash') 실패가 ENOENT입니다. 설치를 다시 해도 후자는 안 고칩니다.


도커와 CI에서만 경로가 비는 경우

이미지에 파일이 없거나 워크스페이스 하위만 체크아웃된 겁니다. 멀티 스테이지 빌드에서 builder의 data/를 runner로 COPY하지 않으면 런타임 ENOENT가 납니다. CI 캐시가 node_modules만 복구하고 생성 파일을 빼도 같습니다.


깃허브 액션 defaults.run.working-directory: app를 쓰면, 루트의 data/는 안 보입니다. 경로는 그 하위 기준이거나, 한 스텝만 working-directory를 루트로 되돌리세요. 공식 문법은 워크플로 working-directory입니다.


포트 거절은 EADDRINUSE, 모듈 이름은 앞 글, 노드 버전은 미즈입니다. 이 글은 경로 문자열만 봅니다.


환경빠지기 쉬운 파일확인
CI.env, 로컬 생성 JSONgitignore, Secrets
도커COPY 누락, .dockerignore이미지 안 ls
액션 하위 디렉터리루트 data/working-directory

도커 이미지에 데이터 폴더가 빠져 ENOENT가 나는 개념 이미지
런타임 스테이지로 data를 COPY하지 않으면 컨테이너 안에서 경로가 비어 있다

참고 자료


내부 연계: Cannot find module, 포트 점유, 폴더 환경, 깃 워크트리, 노드 권한 크론


인용한 동작은 2026년 8월 공개 문서 기준입니다.


자주 묻는 질문

로컬에 파일이 있는데 크론만 ENOENT입니다.

크론 cwd가 홈인 경우가 많습니다. 에러의 절대 경로가 홈 아래로 시작하면 WorkingDirectory나 cd를 넣으세요. 터미널 테스트는 홈에서 같은 명령을 쳐야 재현됩니다.


npm run은 되고 node scripts는 실패합니다.

npm run은 package.json 폴더가 cwd입니다. 셸에서 node를 직접 치면 지금 있는 폴더가 cwd입니다. 스크립트는 __dirname으로 고치거나, 항상 루트에서 실행하세요.


__dirname을 쓰면 ESM에서 에러가 납니다.

ESM에는 __dirname이 없습니다. fileURLToPath(import.meta.url)로 디렉터리를 만드세요. package.json type module이면 CommonJS 예제를 그대로 붙이면 안 됩니다.


CI에서 .env를 못 찾습니다.

.env는 gitignore라 체크아웃에 없습니다. 액션 Secrets나 호스트 환경으로 넣고, 샘플만 커밋하세요. 파일이 꼭 필요하면 생성 스텝을 워크플로에 두세요.


도커에선 로컬 경로가 그대로 필요합니다.

컨테이너 안에는 맥의 /Users 경로가 없습니다. WORKDIR와 COPY로 이미지 안 경로를 맞추세요. 호스트 바인드 마운트를 쓰면 그 마운트 지점이 실제 경로입니다.


Cannot find module과 동시에 뜹니다.

스택에서 먼저 난 줄을 보세요. require 실패는 모듈 글, readFile 실패는 이 글입니다. 둘 다 cwd가 홈이면 한 번에 고칩니다.


ENOENT는 그 경로 문자열이 지금 cwd 기준으로 없다는 뜻입니다. 에러의 절대 경로를 열고, 크론은 홈에서 재현한 뒤, 고정 파일은 스크립트 옆 경로로 바꾸세요. 관련 글: 모듈을 찾을 수 없음, 폴더 환경, 크론 권한.


ENOENTno such filecwdprocess.cwd__dirname크론npmNode.js도커깃허브 액션개발자경로

함께 보면 좋은 문제 해결

EXPLORE / 개발자 작업환경

이어서 읽어보기

전체 토픽 둘러보기