AI CLI 의 --resume 이 세션 ID 를 알아도 못 찾는 이유 — cwd 스코프
세션 ID 를 정확히 넘겼는데 No conversation found 가 뜬다. 세션이 작업 디렉터리 단위로 스코프되기 때문이다. 최소 재현과 실제 해결.
계정을 바꿔 로그인한 뒤, 어제 돌리던 세션을 이어가려고 --resume 에 세션 ID 를 그대로 붙였다. ID 는 화면에서 복사한 정확한 값이고, 트랜스크립트 파일도 디스크에 멀쩡히 있었다. 그런데 CLI 는 그런 세션이 없다고 잘라 말했다.
$ claude --resume ddf20018-85c0-460d-9d3b-83c5aed62d40
No conversation found with session ID: ddf20018-85c0-460d-9d3b-83c5aed62d40
파일은 있는데 못 찾는다. 처음엔 계정을 바꿔서 저장소가 갈렸나 의심했지만, 원인은 그게 아니었다.
다른 디렉터리에서 부르면 바로 터진다
재현은 단순하다. 세션을 만든 디렉터리가 아닌 다른 디렉터리에서 --resume 을 부르면 된다.
$ cd ~/work/project-a
$ claude -p "say OK" # 여기서 세션 하나 생성됨
$ cd ~/work/project-b
$ claude --resume <그 세션 ID> # → No conversation found
-c / --continue(가장 최근 세션 이어가기)도 똑같다. 조건은 하나로 요약된다 — resume 하는 시점의 현재 디렉터리(cwd)가 세션을 만든 디렉터리와 다르면 ID 가 정확해도 실패한다.
특히 잘 걸리는 상황이 몇 가지 있다.
- worktree 안에서 시작한 세션을 레포 루트에서 이어가려 할 때
- 계정을 전환한 뒤 홈 디렉터리에서 무심코
--resume을 칠 때 - 세션을 만든 worktree 디렉터리를 이미 지워버린 뒤
실패했던 접근 — 파일을 눈으로 찾기
처음엔 “저장소가 갈렸겠지” 싶어 트랜스크립트를 직접 뒤졌다. 세션 기록은 이렇게 저장된다.
~/.claude/projects/<인코딩된 cwd>/<sessionId>.jsonl
find 로 파일은 금방 나온다.
$ find ~/.claude/projects -name "ddf20018-*.jsonl"
~/.claude/projects/-home-user-work-project-a-.../ddf20018-....jsonl
파일이 있으니 폴더명을 보고 원래 cwd 를 역산해서 거기로 cd 하면 되겠다고 생각했다. 그런데 폴더명 인코딩이 손실적이라 눈으로 역디코딩하면 틀린다. 인코딩 규칙은 대략 이렇다.
folder = re.sub(r'[^a-zA-Z0-9]', '-', cwd) # 영숫자가 아니면 전부 '-'
/ 도, . 도, _ 도, 공백도 전부 똑같이 - 로 바뀐다(대문자는 보존). 즉 폴더명의 - 하나만 보고는 원래 문자가 슬래시였는지 점이었는지 알 수 없다. 예를 들어 project-a 라는 조각이 실제로는 project/a 였을 수도, project.a 였을 수도 있다. 폴더명을 보고 추측한 경로로 cd 하면 대개 한 글자가 어긋나 여전히 못 찾는다.
근본 원인 — resume 는 cwd 스코프다
--resume 은 세션 저장소 전체를 뒤지지 않는다. 현재 cwd 를 위 규칙으로 인코딩해서 나온 폴더 하나 안에서만 세션 ID 를 찾는다. cwd 가 다르면 애초에 다른 폴더를 들여다보고 있으니 ID 가 맞아도 “없다”고 답한다.
한 가지 더 헷갈리는 점 — 트랜스크립트가 놓이는 폴더는 세션을 시작한 cwd 가 아니라 마지막 cwd 를 따른다. 세션 도중에 디렉터리를 옮기면(예: worktree 로 진입) .jsonl 이 새 cwd 의 폴더로 따라 이동한다. 그래서 레포 루트에서 시작한 세션이 worktree 폴더 밑에 파일로 남아 있는 일이 생긴다.
50 줄 ~/work/project-a ← 세션 시작
282 줄 ~/work/project-a/.wt/feature-x ← 중간에 worktree 진입
151 줄 ~/work/project-a/.wt/task-7 ← 마지막. 파일 위치를 결정한 cwd
“이 세션은 worktree 에서 시작했다”는 오독이기 쉽다. 파일 위치를 정하는 건 마지막 cwd 다.
실제 해결 — 추측하지 말고 cwd 를 읽어라
폴더명을 역산하려 들지 말고, .jsonl 안에 기록된 cwd 필드를 그대로 읽으면 된다. 그 값이 resume 가 요구하는 정확한 경로다.
SID=ddf20018-85c0-460d-9d3b-83c5aed62d40
# 1) 트랜스크립트 찾기
F=$(find ~/.claude/projects -name "$SID.jsonl" | head -1)
# 2) 진짜 cwd 읽기 (마지막 값이 파일 위치를 결정한 cwd)
jq -r 'select(.cwd) | .cwd' "$F" | tail -1
# → ~/work/project-a/.wt/task-7
# 3) 그 경로로 이동해서 resume
cd ~/work/project-a/.wt/task-7
claude --resume "$SID"
핵심은 2번이다. 폴더명이 아니라 파일 내용의 cwd 를 신뢰한다.
디렉터리가 이미 지워졌다면
스코핑은 순수하게 경로 문자열 → 폴더명 인코딩일 뿐이다. 그 경로가 실제 git 레포인지, worktree 로 등록돼 있는지는 보지 않는다. 그래서 같은 폴더명으로 인코딩되는 아무 빈 디렉터리에서나 resume 가 된다. worktree 를 지웠어도 경로만 다시 만들면 된다.
mkdir -p ~/work/project-a/.wt/task-7
cd ~/work/project-a/.wt/task-7
claude --resume "$SID"
다만 대화 맥락(읽은 파일·실행 결과·결정)은 복원돼도, worktree 를 지웠다면 그때 편집한 실제 파일은 디스크에 없다. AI 는 “이렇게 고쳤다”는 기억만 갖고 이어갈 뿐 파일이 돌아오지는 않는다.
인코딩 충돌로 증명한다
cwd 스코프가 전부라는 걸 통제 실험으로 확인할 수 있다. 서로 다른 실제 경로 두 개를 같은 폴더명으로 인코딩되게 만들어 두면, 한쪽에서 만든 세션이 다른 쪽에서 resume 된다.
mkdir -p /tmp/x/a.b /tmp/x/a-b # 둘 다 ...-tmp-x-a-b 로 인코딩됨
cd /tmp/x/a.b && claude -p "say OK" # 여기서 세션 생성
cd /tmp/x/a-b && claude --resume <sid> -p "say PONG"
# → PONG. 실제 경로가 다른데도 resume 됨 (인코딩된 폴더명이 같으니까)
두 경로는 디스크상 별개 디렉터리지만 인코딩 결과가 같아 같은 저장 폴더를 공유한다. resume 가 경로 실체가 아니라 인코딩된 폴더명만 본다는 증거다.
“No conversation found” 가 cwd 문제가 아닌 경우
-
“No conversation found” 가 늘 cwd 문제는 아니다. 같은 폴더에 대화 메시지가 없는 스텁
.jsonl(서브에이전트 메타데이터 등)이 섞여 있으면 cwd 를 맞춰도 resume 되지 않는다. resume 가능한 파일인지는 엔트리 타입으로 먼저 확인한다.jq -r '.type' "$F" | sort | uniq -c # user/assistant 가 있어야 resume 가능 -
백그라운드로 실행 중인 세션은 에러 문구가 다르다.
is currently running as a background agent같은 메시지가 뜨면 이건 cwd 문제가 아니라 이미 그 세션이 살아 있다는 뜻이다. 이 체크는 전역이라 cwd 가 틀려도 먼저 걸린다. -
저장소가 여러 프로파일에 공유되는 경우가 있다. 계정/프로파일별 설정 디렉터리의
projects/가 심볼릭 링크로 한 곳을 가리키도록 묶여 있으면, 계정을 바꿔도 트랜스크립트는 그대로다 — 즉 “세션을 다른 계정으로 옮기는” 작업은 애초에 필요가 없고, ID 와 올바른 cwd 만 있으면 어느 프로파일에서든 이어진다. 복사 스크립트를 짜기 전에ls -ld ~/.claude*/projects로 링크 여부부터 확인하는 게 빠르다.
예방책은 간단하다. 오래 이어갈 세션이라면 처음부터 이름을 붙여 두면 된다. ID 와 cwd 대신 이름으로 추적되니 이 문제 자체를 우회한다.