Skip to content

fix(cli): find the session when the cache is stale or its cwd moved - #146

Merged
gavin-jeong merged 1 commit into
masterfrom
fix-cli-session-lookup-cache-miss
Jul 28, 2026
Merged

fix(cli): find the session when the cache is stale or its cwd moved#146
gavin-jeong merged 1 commit into
masterfrom
fix-cli-session-lookup-cache-miss

Conversation

@gavin-jeong

Copy link
Copy Markdown
Collaborator

문제

ccx refs가 세션이 디스크에 멀쩡히 있는데도 실패했다:

$ ccx refs
Error: no session found matching project paths: /Users/gavin.jeong/src/sendbird/platform-tools/.worktree/build-civiz

원인

findSessionFile은 tmux pane 경로를 LoadCachedSessions(= ~/.claude/.ccx-cache.gob만 읽음)의 ProjectPath와 완전 일치로 비교한다. 두 가지가 겹쳐서 깨진다.

1. CLI는 캐시를 절대 갱신하지 않는다. cache.save()ScanSessions(TUI 전체 스캔)에만 있다. 마지막 TUI 실행 이후 시작된 세션은 캐시에 아예 없다. 실측: 이 작업을 하던 세션 파일도 캐시에 없었다.

2. 캐시된 ProjectPath가 신뢰할 수 없다. scanSessionStream은 지나가는 isMeta 라인마다 sess.ProjectPath = cwd로 덮어쓴다 (scanner_stream.go:63). 즉 캐시에 남는 건 스캔 시점의 마지막 isMeta cwd다. nested Go module이 있는 worktree에서는 cwd가 두 경로를 오간다:

.../build-civiz                23회
.../build-civiz/ci-visibility   6회

실제 transcript를 잘라서 확인한 결과:

스캔 시점(라인 수) 캐시되는 ProjectPath
500 .../build-civiz/ci-visibility ← 매칭 실패
2100 .../build-civiz/ci-visibility ← 매칭 실패
6100 .../build-civiz

submodule이 마지막이었던 구간에서 TUI가 full scan을 돌리면 그 잘못된 경로가 캐시에 굳고, TUI를 다시 띄우기 전까지 CLI는 계속 실패한다.

수정

매칭을 3단계로 나눴다.

  1. 캐시 완전 일치 — 기존 동작 그대로.
  2. ScanSessionsForPaths로 직접 스캔 — pane 경로에서 인코딩한 프로젝트 디렉터리를 읽는다. 결과를 ProjectPath재필터하지 않는다: 그 필드가 바로 신뢰할 수 없는 값이고, pane 경로로 인코딩한 디렉터리 자체가 실제 연결 고리다. 다시 걸면 이 폴백이 존재하는 이유인 바로 그 miss를 재현한다. 이 단계가 원인 1·2를 모두 해결한다.
  3. 상위 디렉터리 폴백 — pane이 세션 프로젝트의 하위(nested module)에 있는 경우. 완전 일치가 아니므로 stderr로 어떤 경로를 썼는지 알린다.

3단계는 의도적으로 단방향이다. 하위 방향도 매칭하면 ~/src 같은 넓은 상위에 있는 pane이 그 아래 아무 깊은 세션이나 주워 온다 — 테스트 중 실제로 관측했고, 실패보다 나쁘다.

per-path 루프는 matchSessionsForPaths(sessions, paths, match)로 추출했고, 우선순위를 경로 거리 → ModTime 으로 잡았다. 먼 조상이 최근이라는 이유로 가까운 부모를 이기지 않는다.

검증

실제 transcript로 원본 실패를 재현한 뒤 수정본과 비교했다 (캐시 ProjectPath가 nested module, pane은 worktree 루트):

cached ProjectPath: ".../build-civiz/ci-visibility"
pane path:          ".../build-civiz"

OLD: Error: no session found matching project paths: .../build-civiz
NEW: OK: id=f861a07b msgs=134

internal/cli/find_session_test.go에 테스트 8개 추가. 회귀 탐지력도 확인했다 — 거리 우선순위를 제거하니 의도대로 실패한다:

--- FAIL: TestMatchSessionsForPathsPrefersNearestAncestor
    matchSessionsForPaths = [grandparent], want [parent]

go test ./... 전체 통과.

`ccx refs` (and every other CLI subcommand) failed with
"no session found matching project paths: ..." while the session plainly
existed on disk.

findSessionFile matched the tmux pane's path against LoadCachedSessions,
which only reads ~/.claude/.ccx-cache.gob. Two problems:

1. Nothing in the CLI writes that cache — cache.save() runs only in
   ScanSessions, i.e. the TUI's full scan. A session started since the
   last TUI run is simply absent.

2. scanSessionStream sets sess.ProjectPath from every isMeta line it
   passes, so the cached value is whichever cwd the transcript's *last*
   isMeta line happened to carry at scan time. For a worktree with a
   nested Go module the cwd alternates between the two, so the cached
   path can be the submodule while the pane sits at the worktree root.
   Verified on a real transcript: truncated at 500 lines it caches
   .../build-civiz/ci-visibility, at 6100 lines .../build-civiz.

Matching is now three passes:

- exact match against the cache (unchanged);
- ScanSessionsForPaths on the pane's project dirs. Its results are
  deliberately NOT re-filtered by ProjectPath — that field is the
  unreliable one; the directory, encoded from the pane path, is the
  actual link. This pass fixes both problems above;
- a session at an ancestor of the pane path, for a pane inside a nested
  module, reported on stderr since it isn't an exact match. Only this
  direction: matching downward would let a pane in a broad parent like
  ~/src adopt an arbitrary deep session under it, which is worse than
  failing (observed while testing).

The per-path loop is extracted into matchSessionsForPaths with a
pluggable predicate, and ranks candidates by path distance before
ModTime so a nearer ancestor beats a more recently touched distant one.
@Kairo-Kim Kairo-Kim added the auto-review/approved Auto-approved by the Slack auto-reviewer bot label Jul 28, 2026

@jinsekim jinsekim left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@gavin-jeong
gavin-jeong merged commit 5d63cf5 into master Jul 28, 2026
3 checks passed
@gavin-jeong
gavin-jeong deleted the fix-cli-session-lookup-cache-miss branch July 28, 2026 03:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-review/approved Auto-approved by the Slack auto-reviewer bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants