multiple candidate TSConfigRootDirs — 저장소 안에 worktree 를 만들면 ESLint 가 전부 터진다
repo 내부에 git worktree 를 두면 tsconfig.json 이 두 벌 존재해 typescript-eslint 의 tsconfigRootDir 자동탐지가 후보를 결정하지 못한다. 208개 파일이 전부 파싱에러.
작업하던 저장소 안에 git worktree 를 하나 만들었다. 그다음 pnpm exec eslint . 를 돌렸더니 모든 .ts / .tsx 파일이 파싱에러를 냈다.
error Parsing error: No tsconfigRootDir was set, and multiple candidate TSConfigRootDirs are present:
- /home/user/prj/myapp
- /home/user/prj/myapp/.claude/worktrees/prep-cleanup
You'll need to explicitly set tsconfigRootDir in your parser options.
See: https://tseslint.com/parser-tsconfigrootdir
208개 파일에 208개 에러. 한 파일도 통과하지 못했다. 코드는 한 줄도 안 고쳤는데.
lint 를 직접 부르지 않아도 같은 곳에서 터진다. 에디터 저장 훅, pnpm verify, pre-push 훅, CI — TypeScript 프로젝트를 파싱하는 경로면 전부다.
원인
git worktree 를 working tree 내부에 만들면 그 디렉터리 안에도 tsconfig.json 과 eslint.config.js 가 똑같이 존재한다. 워크트리는 브랜치의 완전한 체크아웃이니 당연하다.
eslint . 의 . 은 저장소 루트다. 여기서 아래로 훑어 내려가다 워크트리 디렉터리까지 들어간다. typescript-eslint 는 프로젝트 루트를 자동으로 찾으려 하는데, 이때 후보가 두 개 잡힌다.
- 저장소 루트
- 중첩된 워크트리 루트
어느 쪽을 기준으로 상대경로를 풀어야 할지 결정할 수 없으니 에러를 낸다. 조용히 잘못된 쪽을 고르는 것보다는 나은 설계지만, 처음 보면 원인이 안 보인다.
Claude Code 의 EnterWorktree 툴이 기본으로 워크트리를 .claude/worktrees/ 아래에 만든다. 저장소 내부다. 그래서 이 함정에 그대로 빠진다.
해결 — 워크트리를 저장소 바깥에 만든다
eslint . 의 스캔 범위 밖으로 빼면 내려갈 일 자체가 없어진다.
# in-repo worktree 제거. 브랜치 ref 는 보존된다 — worktree remove 는 브랜치를 지우지 않는다
git worktree remove .claude/worktrees/<name> --force
# 저장소 바깥 형제 디렉터리로 재생성 (기존 브랜치 재사용)
git worktree add ../myapp.wt/<name> <existing-branch>
# 또는 새 브랜치로
git worktree add ../myapp.wt/<name> -b feature/<slug>
EnterWorktree 로 이미 만들어둔 상태라면 순서가 있다.
ExitWorktree action=keep— 브랜치를 보존한 채 빠져나온다git worktree remove- 바깥에
git worktree add
브랜치가 지워지는 게 아니므로 커밋을 잃지 않는다.
in-repo 를 유지해야 한다면
두 가지 우회가 있다. lint 설정에서 워크트리를 무시하거나,
{ ignores: ['.claude/worktrees/**'] }
parser options 에 루트를 명시하거나.
parserOptions: { tsconfigRootDir: import.meta.dirname }
둘 다 에러는 없앤다. 그래도 나는 바깥 형제 디렉터리를 권한다. node_modules 와 각종 툴링이 중첩 디렉터리를 만나 생기는 다른 충돌들도 같이 사라지기 때문이다. 이 에러는 그중 가장 먼저 눈에 띈 하나일 뿐이다.
바깥으로 뺀 뒤의 대가
형제 디렉터리 워크트리에는 node_modules 가 없다. 그쪽에서 pre-push 훅이 pnpm verify 를 돌리면 깨진다.
둘 중 하나로 처리한다.
- 워크트리에서
pnpm install을 한 번 돌린다 - 소스가 아닌 변경(문서, 설정)이면
git push --no-verify
디스크가 아깝다면 pnpm 의 store 를 공유하는 구성이라 실제 중복은 생각보다 적다.
정리
worktree 를 저장소 안에 두면 “설정 파일이 두 벌”이라는 상태가 만들어진다. 이걸 견디지 못하는 도구는 typescript-eslint 만이 아니다. 루트를 자동 탐지하는 도구는 대체로 같은 방식으로 헷갈린다.
그래서 규칙을 단순하게 잡았다. 워크트리는 항상 저장소 바깥 형제 디렉터리에. 경로 하나 더 치는 대신 도구 전체가 조용해진다.