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.jsoneslint.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 로 이미 만들어둔 상태라면 순서가 있다.

  1. ExitWorktree action=keep — 브랜치를 보존한 채 빠져나온다
  2. git worktree remove
  3. 바깥에 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 만이 아니다. 루트를 자동 탐지하는 도구는 대체로 같은 방식으로 헷갈린다.

그래서 규칙을 단순하게 잡았다. 워크트리는 항상 저장소 바깥 형제 디렉터리에. 경로 하나 더 치는 대신 도구 전체가 조용해진다.