feat: worktree-resume integration (worktree 名 = session 名 + RESUME/NEW picker)#43
Conversation
worktree 名 = session 名 の 1:1 マッピングを ccw が裏で確立し、picker から の worktree 選択でそのまま過去会話を resume できる体験を設計。L2 4 行レ イアウト + RESUME/NEW バッジ + footer ランダム TIPS を含む。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ccw が CLI ツールとしてユーザー権限で動作する以上、~/.claude/ 読み取りに 追加の権限プロンプトは発生しない。マーカーファイルの管理コストや exit 前 クラッシュ時の整合性問題を避けるため、Claude Code が出力する session ログ ディレクトリを直接参照する方式に切り替える。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
spec を 13 タスクに分解。各タスクは TDD で進められる粒度。 namegen / has_session / tips の 3 つの新規パッケージを段階的に追加し、 最後に cmd/ccw を新シグネチャへ移行する。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CGo 有効ビルドで os.UserHomeDir() が HOME 環境変数を尊重しない macOS の挙動を回避し、t.Setenv によるテスト分離を確実にする。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
picker が描画時に resume 可否を判定できるよう Info にキャッシュする。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
worktree 行先頭に表示するセッション resume 可否バッジを追加。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
resume バッジ・status・indicators を 1 行目に集約し、branch / pr / path を 個別行で表示。arrow セパレータは廃止。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
picker footer 用の短いヒント文字列を deterministic に選択する。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Model 構築時に tips.PickRandom で TIPS を 1 件選び、stateList の View 末尾に 表示。gh 不在時のヒントは引き続き表示する。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 新規 worktree 起動時に namegen.Generate() でセッション名を生成 - picker から既存 worktree を選択した場合は claude.Continue を呼び、 HasSession が false かつ非ゼロ exit のときに -n <name> でフォールバック - Selection.HasSession を追加し UI から CLI へ可否を伝搬 - tips_test.go: SA4000 を修正(同一式の比較を変数化) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fake claude バイナリで ccw -n の引数遷移を検証する。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
picker run の挙動を --continue ベースの新仕様に置き換え、Claude Code の 最低バージョンを 2.1.118 に引き上げた。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
セッション無しの worktree でも常に claude --continue を試行していたため、 無駄な subprocess 起動と、claude が picker を開いた場合のネスト UI 化リスクが あった。HasSession で先に分岐し、無ければ直接 -n <name> で起動する。 ロジックは runResume ヘルパに切り出し、runPicker の cyclomatic 複雑度を抑える。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 43 minutes and 53 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR implements session-aware worktree naming and resume integration for Claude Code. It adds human-readable name generation for new worktrees, detects existing Claude Code sessions via filesystem checks, uses Changes
Sequence DiagramsequenceDiagram
participant User
participant ccw
participant NameGen as Name Generator
participant WorktreeSession as Session Detector
participant Claude as Claude CLI
participant FS as File System
User->>ccw: Select worktree or use -n flag
ccw->>WorktreeSession: HasSession(worktreePath)?
WorktreeSession->>FS: Check ~/.claude/projects/<encoded>/
FS-->>WorktreeSession: Session .jsonl file exists?
WorktreeSession-->>ccw: true/false
alt Session Exists
ccw->>Claude: Continue(args)
Claude->>Claude: Execute with --continue
Claude-->>ccw: exit code
else No Session
ccw->>NameGen: Generate()
NameGen-->>ccw: adjective-noun-hex
ccw->>Claude: LaunchNew(name, args)
Claude->>Claude: Execute with --worktree <name> -n <name>
Claude-->>ccw: exit code
end
ccw-->>User: Return exit code
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/picker/delegate.go (1)
113-122:⚠️ Potential issue | 🟡 Minor
truncateToWidthcan split multi-byte UTF-8 runes.The byte-level slicing
s = s[:len(s)-1]can leave a partial UTF-8 sequence at the tail when truncating non-ASCII strings (branch names, PR titles, paths). This may causelipgloss.Widthto misreport width or render replacement characters. The function is also O(n²) due to repeatedlipgloss.Widthcalls. Truncate by rune instead using[]rune(s)to respect UTF-8 boundaries.🛡️ Proposed rune-safe trim
func truncateToWidth(s string, n int) string { if lipgloss.Width(s) <= n { return s } - for len(s) > 0 && lipgloss.Width(s) > n { - s = s[:len(s)-1] - } - return s + runes := []rune(s) + for len(runes) > 0 && lipgloss.Width(string(runes)) > n { + runes = runes[:len(runes)-1] + } + return string(runes) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/picker/delegate.go` around lines 113 - 122, truncateToWidth currently slices bytes and can break UTF-8 runes and is inefficient; convert the string to a []rune, iterate runes computing a cumulative visible width (use lipgloss.Width(string(r))) and keep runes until adding the next rune would exceed n, then return string(runes[:k]); ensure you compare the initial full-string width (lipgloss.Width(s)) first to avoid work, and reference the truncateToWidth function and lipgloss.Width when making the change.
🧹 Nitpick comments (9)
internal/picker/style.go (1)
105-113: Doc nit: the comment understates the colored case.The comment only describes the
NO_COLORoutput; consider noting that the colored variant also padsNEWwith trailing spaces so the label segments are equal width (modulo emoji width caveats).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/picker/style.go` around lines 105 - 113, Update the function comment for ResumeBadge (and mention noColor()) to state that in the colored branch the "NEW" label is also padded with trailing spaces so the label segments render at equal width (not just the NO_COLOR case), e.g., mention that both the noColor() branch and the colored variant pad "NEW" to match "[RESUME]" width (noting emoji width caveats if desired).internal/picker/model.go (1)
167-167: Optional: split long return for readability.The single-line
Model{...}literal is fairly dense now thattipis included. Splitting fields onto multiple lines would also reduce diff churn for future additions, but this is purely cosmetic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/picker/model.go` at line 167, The return statement constructing the Model is dense; update the return in the function that currently does return Model{state: stateList, infos: infos, list: l, ghAvailable: gh.Available(), tip: tips.PickRandom(uint64(time.Now().UnixNano()))} to a multi-line composite literal so each field is on its own line (e.g. Model{ state: stateList, infos: infos, list: l, ghAvailable: gh.Available(), tip: tips.PickRandom(uint64(time.Now().UnixNano())), }) to improve readability and reduce future diff churn.internal/picker/style_test.go (1)
64-78: Optional: assert ANSI escape presence in colored test for parity.
TestPRBadge_ColoredContainsLabel(Lines 40-42) verifies an ANSI escape is present when colors are enabled, butTestResumeBadge_Coloredonly checks for the literal label substring. SinceResumeBadgereturns the plain[RESUME]/[NEW]string underNO_COLOR, a substring match alone won't actually distinguish colored output from a misconfigured no-color path. Consider adding astrings.Contains(got, "\x1b[")check for symmetry.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/picker/style_test.go` around lines 64 - 78, TestResumeBadge_Colored only checks for the plain label substring and doesn't assert that ANSI escapes are present; update TestResumeBadge_Colored (which sets NO_COLOR="" and manipulates lipgloss.SetColorProfile) to also assert that the returned string from ResumeBadge(true/false) contains an ANSI escape sequence (e.g. strings.Contains(got, "\x1b[")) similar to TestPRBadge_ColoredContainsLabel so the test verifies colored output rather than just the label.tests/resume_flow_test.go (1)
11-27: Consider gating this integration test behind a build tag.
setupFakeEnvrebuilds bothfake_claudeand the fullccwbinary on every invocation, so this test always runs (and rebuilds) under a plaingo test ./.... For a fast inner dev loop you may want a//go:build integrationtag on this file (or a-shortskip), and a separate Make target / CI job that runs the integration suite. Not blocking — flagging for future hygiene.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/resume_flow_test.go` around lines 11 - 27, The test rebuilds binaries every run; gate it by adding an integration build tag or a short-test skip: either add a top-of-file build constraint (e.g., //go:build integration) so the whole file is compiled only for integration runs, or modify setupFakeEnv (and/or Test entry) to check testing.Short() and call t.Skip("skipping integration test in short mode"); ensure references to buildBinary and setupFakeEnv remain and that CI/Make invokes go test with the integration tag or without -short accordingly.internal/picker/delegate_test.go (1)
138-140: Brittle assertion:#may appear in path or branch in future fixtures.
strings.Contains(got, "#")is a coarse check that could trigger on any#in a worktree path or branch name. Consider asserting on a more specific string (e.g."#42", or that the renderedpr:line is empty after the label).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/picker/delegate_test.go` around lines 138 - 140, The test currently uses strings.Contains(got, "#") which is too coarse; update the assertion to check for a PR-number pattern or an empty pr: value instead. Replace the strings.Contains(got, "#") check with either a regex search for a hash followed by digits (e.g. use regexp.MustCompile(`#\d+`) and assert it does NOT match got) or parse the rendered output to find the "pr:" line and assert that the value after the "pr:" label is empty; target the same variable (got) and remove the old strings.Contains check in delegate_test.go.cmd/ccw/main.go (1)
267-274: Consolidate duplicateworktreeNamehelpers usingpath.Base().Both
cmd/ccw/main.go:267andinternal/picker/delegate.go:88define identicalworktreeNamefunctions that extract the last path component—they're hand-rolled equivalents of stdlib'spath.Base(). Since paths come fromgit worktree list --porcelainand don't have trailing slashes, usingpath.Base()is semantically correct and eliminates duplication.♻️ Proposed simplification
-func worktreeName(path string) string { - for i := len(path) - 1; i >= 0; i-- { - if path[i] == '/' { - return path[i+1:] - } - } - return path -} +func worktreeName(p string) string { + return path.Base(p) +}Either consolidate by exporting one helper to a shared package, or simplify both to use stdlib and accept local duplication.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/ccw/main.go` around lines 267 - 274, The two identical helpers named worktreeName (used in cmd/ccw/main.go and internal/picker/delegate.go) should be replaced with the stdlib path.Base() call to remove duplicate hand-rolled logic; update each occurrence of worktreeName to call path.Base(path) (importing "path" as needed), or alternatively move a single exported helper into a shared package and have both files call that helper—ensure imports are added and remove the duplicated function definitions.docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.md (2)
199-203: E2 fallback wording vs. likely implementation reality.E2 specifies "即時非ゼロ exit なら
claude -n <name>で再試行" but Go'sexec.Cmd.Run()does not surface "immediate vs delayed" — only the final exit code. As written, any non-zero exit from a long, fully-interactive--continuesession (user/exit 1, crash mid-conversation, Ctrl-C with non-zero code) will trigger a fresh-session relaunch, which is almost certainly not what the user wants and effectively swallows their exit intent.Recommend tightening this section to either:
- Drop the "即時" qualifier and accept the simpler "any non-zero ⇒ retry" semantics, and explicitly call out the UX trade-off (and how to opt out), or
- Specify a measurable heuristic (e.g., "exit within N ms of spawn" or "stderr matches
no session found") and require the implementation to honor it.This will keep the spec honest about what the resume/fallback flow actually guarantees.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.md` around lines 199 - 203, The spec's "即時非ゼロ exit" wording is inconsistent with Go's exec.Cmd.Run() semantics (which only provides final exit codes), so update the E2 section to remove the misleading "即時" qualifier and either (A) state the simpler rule "any non-zero exit ⇒ retry with claude -n <name>" and document the UX trade-off and opt-out, or (B) replace it with a concrete, implementable heuristic (e.g., require the child process to exit within N ms of Start() or match stderr text like "no session found") that implementations using exec.Cmd.Run()/Start() and checks around HasSession()/ReadDir must follow; reference the symbols HasSession(), ReadDir, and exec.Cmd.Run()/Start() in the text so implementers know which behaviors the heuristic targets.
9-9: Avoid hard-codedREADME.ja.md:79-80line references.The spec references
README.ja.md:79-80in four places as the location of the old--resumewarning. Once this PR lands and the warning is removed/rewritten, those line numbers become stale and misleading for future readers of the spec. Consider replacing the line references with a quoted snippet of the warning text (or a permalink to a pinned commit) so the spec remains self-contained after the README is edited.Also applies to: 22-22, 318-318, 339-339
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.md` at line 9, Replace the hard-coded "README.ja.md:79-80" line references in this spec with a self-contained reference: either include the exact quoted warning text about "`-- --resume ID`" (so readers can see the deprecated message inline) or link to a stable permalink of the README commit that contains the original warning; update all occurrences in the spec (the four places that currently point to README.ja.md lines) to use the quoted snippet or permalink and remove any line-number references.internal/claude/claude.go (1)
12-22: Consider guarding against an emptyname.
BuildNewArgsunconditionally injects--worktree <name> -n <name>. If a caller ever passesname == ""(e.g., a future code path wherenamegen.Generate()returns empty under some failure mode, or a refactor that forgets to populate it), the resulting argv becomes--worktree "" -n "", whichclaudewill likely reject with a confusing error and which is silently incorrect from ccw's side. Since this function is the single chokepoint for new-session arg construction, a one-line guard here is cheap insurance:🛡️ Optional defensive check
func BuildNewArgs(name, preamble string, extra []string) []string { + if name == "" { + // Caller bug: name must be non-empty for `--worktree`/`-n`. + // Falling through would produce `--worktree "" -n ""`, which claude rejects. + panic("claude.BuildNewArgs: name must not be empty") + } args := make([]string, 0, 6+len(extra)+2) args = append(args, "--permission-mode", "auto", "--worktree", name, "-n", name)A returned error is also fine if you'd rather not panic; the point is to fail loudly at the boundary. Skip if you're confident every call site already validates upstream.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/claude/claude.go` around lines 12 - 22, Add a defensive check at the top of BuildNewArgs to ensure the provided name is non-empty; if name == "" then fail loudly (e.g., panic with a clear message like "BuildNewArgs: empty worktree/name") instead of building args with --worktree "" -n "". This ensures BuildNewArgs never returns an argv containing empty --worktree/-n values and isolates the error at the boundary without needing to change the function signature.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cmd/ccw/main.go`:
- Around line 134-147: The current fallback unconditionally treats any non-zero
exitCode from claude.Continue(sel.Path, passthrough) as a missing session and
launches a new conversation via claude.LaunchNew, which can restart after
intentional quits or transient errors; change the flow to only auto-fallback
when the session was actually removed: after Continue returns with a non-zero
code (and nil error), call worktree.HasSession(sel.Path) (or equivalent) and
only call claude.LaunchNew(sel.Path, name, "", passthrough) when HasSession
returns false; otherwise propagate or report the original exit code to the user
(do not auto-relaunch on any non-zero code). Ensure you still handle actual
errors from Continue/LaunchNew as before using ui.Error.
- Around line 124-148: The call sites in runResume that invoke
claude.LaunchNew(sel.Path, name, "", passthrough) currently always pass a
worktree flag (via how LaunchNew builds args); change runResume to detect when
sel.Path is already a git worktree and in that case call LaunchNew without the
--worktree flag (i.e., only pass the -n name option) so behavior matches the
README; locate the logic around runResume, worktreeName, sel.Path and
sel.HasSession and conditionally select the LaunchNew invocation variant (or add
a helper like isWorktree(sel.Path) and use it to choose the flag-less LaunchNew
call) to avoid name-collision errors when the named worktree already exists
elsewhere.
In `@docs/README.ja.md`:
- Line 116: Update the README entry that currently requires "Claude Code `>=
2.1.118`" to either lower the minimum to `>= 2.1.76` (since `-n / --name` and
`--worktree` were available from 2.1.76 and 2.1.49 respectively) or explicitly
state what additional feature in 2.1.118+ is needed; modify the sentence
referencing "Claude Code `>= 2.1.118` — ccw は `--worktree <name>` と `-n <name>`
..." accordingly so it accurately reflects the true minimum version or the
specific feature introduced in 2.1.118.
In `@docs/superpowers/plans/2026-04-25-worktree-resume-integration.md`:
- Around line 1300-1317: The plan and pseudocode expect the fallback to
LaunchNew only when claude.Continue returns non-zero AND sel.HasSession is
false; update the implementation in runResume (cmd/ccw/main.go) so the fallback
call to claude.LaunchNew(sel.Path, name, "", passthrough) is guarded by code !=
0 && !sel.HasSession (instead of unconditionally on code != 0), keeping the same
error handling via ui.Error for both claude.Continue and claude.LaunchNew and
using worktreeName(sel.Path) for the fallback name.
In `@docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.md`:
- Around line 57-69: The spec's encoding rule is wrong: update the spec text so
the encoding replaces `/` → `-` only (do not replace `.`), correct the example
`/Users/foo/repo/.claude/worktrees/bar` →
`-Users-foo-repo-.claude-worktrees-bar`, and note that HasSession() checks
`~/.claude/projects/<encoded-cwd>/*.jsonl` using this `/`-only encoding;
additionally add guidance to the spec to enable a debug mode (e.g., CCW_DEBUG=1)
that logs the candidate encoded directory and lists the actual children of
`~/.claude/projects/` so encoding mismatches are diagnosable in the field.
In `@internal/worktree/has_session.go`:
- Around line 9-15: The EncodeProjectPath currently replaces both '/' and '.'
with '-', which may be incorrect; inspect Claude Code's actual encoding (check a
real ~/.claude/projects/ or the upstream source) and if dots are preserved,
change EncodeProjectPath to only replace '/' (and if documented, spaces and
underscores) with '-' instead of replacing '.'; update any callers/tests (e.g.,
HasSession) to use the revised EncodeProjectPath and add a unit test that
verifies encoding for inputs with dots (and consider a later fallback scan of
~/.claude/projects/ for matching cwd if no session is found).
---
Outside diff comments:
In `@internal/picker/delegate.go`:
- Around line 113-122: truncateToWidth currently slices bytes and can break
UTF-8 runes and is inefficient; convert the string to a []rune, iterate runes
computing a cumulative visible width (use lipgloss.Width(string(r))) and keep
runes until adding the next rune would exceed n, then return string(runes[:k]);
ensure you compare the initial full-string width (lipgloss.Width(s)) first to
avoid work, and reference the truncateToWidth function and lipgloss.Width when
making the change.
---
Nitpick comments:
In `@cmd/ccw/main.go`:
- Around line 267-274: The two identical helpers named worktreeName (used in
cmd/ccw/main.go and internal/picker/delegate.go) should be replaced with the
stdlib path.Base() call to remove duplicate hand-rolled logic; update each
occurrence of worktreeName to call path.Base(path) (importing "path" as needed),
or alternatively move a single exported helper into a shared package and have
both files call that helper—ensure imports are added and remove the duplicated
function definitions.
In `@docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.md`:
- Around line 199-203: The spec's "即時非ゼロ exit" wording is inconsistent with Go's
exec.Cmd.Run() semantics (which only provides final exit codes), so update the
E2 section to remove the misleading "即時" qualifier and either (A) state the
simpler rule "any non-zero exit ⇒ retry with claude -n <name>" and document the
UX trade-off and opt-out, or (B) replace it with a concrete, implementable
heuristic (e.g., require the child process to exit within N ms of Start() or
match stderr text like "no session found") that implementations using
exec.Cmd.Run()/Start() and checks around HasSession()/ReadDir must follow;
reference the symbols HasSession(), ReadDir, and exec.Cmd.Run()/Start() in the
text so implementers know which behaviors the heuristic targets.
- Line 9: Replace the hard-coded "README.ja.md:79-80" line references in this
spec with a self-contained reference: either include the exact quoted warning
text about "`-- --resume ID`" (so readers can see the deprecated message inline)
or link to a stable permalink of the README commit that contains the original
warning; update all occurrences in the spec (the four places that currently
point to README.ja.md lines) to use the quoted snippet or permalink and remove
any line-number references.
In `@internal/claude/claude.go`:
- Around line 12-22: Add a defensive check at the top of BuildNewArgs to ensure
the provided name is non-empty; if name == "" then fail loudly (e.g., panic with
a clear message like "BuildNewArgs: empty worktree/name") instead of building
args with --worktree "" -n "". This ensures BuildNewArgs never returns an argv
containing empty --worktree/-n values and isolates the error at the boundary
without needing to change the function signature.
In `@internal/picker/delegate_test.go`:
- Around line 138-140: The test currently uses strings.Contains(got, "#") which
is too coarse; update the assertion to check for a PR-number pattern or an empty
pr: value instead. Replace the strings.Contains(got, "#") check with either a
regex search for a hash followed by digits (e.g. use regexp.MustCompile(`#\d+`)
and assert it does NOT match got) or parse the rendered output to find the "pr:"
line and assert that the value after the "pr:" label is empty; target the same
variable (got) and remove the old strings.Contains check in delegate_test.go.
In `@internal/picker/model.go`:
- Line 167: The return statement constructing the Model is dense; update the
return in the function that currently does return Model{state: stateList, infos:
infos, list: l, ghAvailable: gh.Available(), tip:
tips.PickRandom(uint64(time.Now().UnixNano()))} to a multi-line composite
literal so each field is on its own line (e.g. Model{ state: stateList, infos:
infos, list: l, ghAvailable: gh.Available(), tip:
tips.PickRandom(uint64(time.Now().UnixNano())), }) to improve readability and
reduce future diff churn.
In `@internal/picker/style_test.go`:
- Around line 64-78: TestResumeBadge_Colored only checks for the plain label
substring and doesn't assert that ANSI escapes are present; update
TestResumeBadge_Colored (which sets NO_COLOR="" and manipulates
lipgloss.SetColorProfile) to also assert that the returned string from
ResumeBadge(true/false) contains an ANSI escape sequence (e.g.
strings.Contains(got, "\x1b[")) similar to TestPRBadge_ColoredContainsLabel so
the test verifies colored output rather than just the label.
In `@internal/picker/style.go`:
- Around line 105-113: Update the function comment for ResumeBadge (and mention
noColor()) to state that in the colored branch the "NEW" label is also padded
with trailing spaces so the label segments render at equal width (not just the
NO_COLOR case), e.g., mention that both the noColor() branch and the colored
variant pad "NEW" to match "[RESUME]" width (noting emoji width caveats if
desired).
In `@tests/resume_flow_test.go`:
- Around line 11-27: The test rebuilds binaries every run; gate it by adding an
integration build tag or a short-test skip: either add a top-of-file build
constraint (e.g., //go:build integration) so the whole file is compiled only for
integration runs, or modify setupFakeEnv (and/or Test entry) to check
testing.Short() and call t.Skip("skipping integration test in short mode");
ensure references to buildBinary and setupFakeEnv remain and that CI/Make
invokes go test with the integration tag or without -short accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 867e0226-5971-4c11-9847-25994623fac3
📒 Files selected for processing (26)
README.mdcmd/ccw/main.godocs/README.ja.mddocs/superpowers/plans/2026-04-25-worktree-resume-integration.mddocs/superpowers/specs/2026-04-25-worktree-resume-integration-design.mdinternal/claude/claude.gointernal/claude/claude_test.gointernal/namegen/namegen.gointernal/namegen/namegen_test.gointernal/picker/delegate.gointernal/picker/delegate_test.gointernal/picker/model.gointernal/picker/run.gointernal/picker/style.gointernal/picker/style_test.gointernal/picker/update.gointernal/picker/view.gointernal/picker/view_test.gointernal/tips/tips.gointernal/tips/tips_test.gointernal/worktree/has_session.gointernal/worktree/has_session_test.gointernal/worktree/worktree.gointernal/worktree/worktree_test.gotests/fakes/fake_claude/main.gotests/resume_flow_test.go
| Claude Code はセッションログを以下に保存する: | ||
|
|
||
| ```text | ||
| ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl | ||
| ``` | ||
|
|
||
| `<encoded-cwd>` は cwd の絶対パスを `/` → `-` および `.` → `-` で置換した文字列。例: worktree `/Users/foo/repo/.claude/worktrees/bar` は `-Users-foo-repo--claude-worktrees-bar` にエンコードされる。 | ||
|
|
||
| ccw は worktree ごとにこのディレクトリを参照し、`*.jsonl` が 1 件以上あれば `HasSession=true` と判定する。 | ||
|
|
||
| - ccw 自身は CLI ツール(=ユーザー権限で動作)なので、`~/.claude/` を読むのに追加の権限プロンプトは発生しない | ||
| - 開発時に Claude Code 経由で ccw を実装する場合は、リポジトリの `.claude/settings.local.json` で `Read(~/.claude/projects/**)` を許可する | ||
| - `git worktree remove` で worktree を消しても `~/.claude/projects/<encoded>/` は残るが、worktree 自体が消えれば picker に出ないので影響なし |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Claude Code ~/.claude/projects directory naming encoding rule for cwd path with dots
💡 Result:
Claude Code names directories in ~/.claude/projects/ by taking the absolute current working directory (CWD) path and replacing path separators (/) with hyphens (-). It also replaces other characters like spaces, backslashes, colons, underscores, and non-ASCII characters with hyphens or sequences of hyphens, resulting in a lossy encoding. For example: - CWD: /Users/me/kb/projects/kicker → ~/.claude/projects/-Users-me-kb-projects-kicker/ - CWD: /Users/foo/Projects/Open Source/checkmate → ~/.claude/projects/-Users-foo-Projects-Open-Source-checkmate/ - Paths with dots like /home/user/my.project remain as-is for dots (since . is allowed in filenames), but / becomes -, so /home/user/my.project → -home-user-my.project This encoding is lossy: paths differing only in / vs - (e.g., my/project vs my-project) or spaces vs - collide into the same directory, potentially mixing session history and memory. Non-ASCII characters become dashes, further reducing readability. Official docs confirm /.claude/projects// stores per-project session data and auto-memory (/.claude/projects//memory/), where derives from the CWD path (git repo root if available, else project root). No reversible encoding is used; it's designed for filesystem compatibility but causes known bugs (e.g., GitHub issues #7009, #9221, #29471). To handle dots specifically: Dots (.) in directory names are preserved unchanged, as they are valid filesystem characters and not replaced. Only path separators and problematic characters are munged to -.
Citations:
- 1: [BUG] Project memory directory naming can collide when paths differ only by / vs - anthropics/claude-code#29471
- 2: [BUG] Lossy project directory path encoding makes decoding impossible anthropics/claude-code#9221
- 3: Project config tied to directory path breaks on file reorganization anthropics/claude-code#24789
- 4: Path Encoding Collision: Directories with dashes conflict with path separators anthropics/claude-code#7009
- 5: Path encoding collision causes session loss on directory reorganization anthropics/claude-code#21085
- 6: https://code.claude.com/docs/en/claude-directory
- 7: https://docs.anthropic.com/en/docs/claude-code/memory
- 8: [FEATURE/BUG] Improve project path encoding to preserve readability and improve third-party tool compatibility anthropics/claude-code#19972
- 9: [BUG] claude code create project folders delimited by "-" but this is a legitimate character in the path anthropics/claude-code#18829
- 10: fix(claude): correct project encoded path siteboon/claudecodeui#451
The spec's path-encoding rule is incorrect and will cause HasSession() failures for paths with dots.
The spec claims encoding replaces both / → - and . → -, with the example /Users/foo/repo/.claude/worktrees/bar → -Users-foo-repo--claude-worktrees-bar. However, Claude Code actually only replaces / with - and preserves dots unchanged (e.g., /home/user/my.project → -home-user-my.project). This means session lookups will silently fail for any path containing a dot—notably the default ccw worktree root .claude/worktrees/...—causing the picker to always render ⚡ NEW instead of resuming existing sessions.
Update the spec to reflect the actual encoding rule (replace / only) and correct the example. Add a debug log (e.g., CCW_DEBUG=1) that prints both the candidate encoded directory and the actual children of ~/.claude/projects/ so encoding mismatches are diagnosable in the field.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.md`
around lines 57 - 69, The spec's encoding rule is wrong: update the spec text so
the encoding replaces `/` → `-` only (do not replace `.`), correct the example
`/Users/foo/repo/.claude/worktrees/bar` →
`-Users-foo-repo-.claude-worktrees-bar`, and note that HasSession() checks
`~/.claude/projects/<encoded-cwd>/*.jsonl` using this `/`-only encoding;
additionally add guidance to the spec to enable a debug mode (e.g., CCW_DEBUG=1)
that logs the candidate encoded directory and lists the actual children of
`~/.claude/projects/` so encoding mismatches are diagnosable in the field.
| // EncodeProjectPath converts an absolute worktree path to the directory name | ||
| // Claude Code uses under ~/.claude/projects/. Both '/' and '.' map to '-'. | ||
| // This rule is observed from claude's behavior; it is not part of any | ||
| // public contract and may change. | ||
| func EncodeProjectPath(absPath string) string { | ||
| return strings.NewReplacer("/", "-", ".", "-").Replace(absPath) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect any locally-checked-out projects directory if present, and look for
# alternative encoding rules (URL-encoding, double-dash for dot, etc.) referenced
# in claude-code release notes or examples in this repo.
rg -nP '\.claude/projects' -C2
rg -nP 'EncodeProjectPath|projects/|encode.*project' -C2 --type=mdRepository: tqer39/ccw-cli
Length of output: 35203
🌐 Web query:
Claude Code ~/.claude/projects directory naming scheme — exact encoding of cwd paths (handling of '.' and '/')
💡 Result:
Claude Code stores project-specific data (sessions, memory, history) in ~/.claude/projects/<encoded-path>/, where <encoded-path> is the absolute current working directory (CWD) path when Claude Code is launched, encoded by replacing '/' (and spaces, underscores) with '-'. The directory name always starts with '-', followed by the sanitized absolute path components joined by '-'. Examples: - CWD /Users/me/kb/projects/kicker → /.claude/projects/-Users-me-kb-projects-kicker/ - Likely /home/user/project/spec-rl/ → -home-user-project-spec-rl Handling of special cases: - '.': If launched from CWD '' (home), likely encodes to ~-Users-username or similar absolute home path (e.g., -Users-me on macOS), not collapsing to single component. Relative '.' is resolved to absolute before encoding. - '/': Root path '/' likely encodes to -/ or just -, but impractical as CWD. The encoding is lossy: paths differing only in '/' vs ' ' or '_' vs '-' collide (e.g., /a/b vs /a-b both → -a-b). No official docs detail exact sanitizePath implementation, but consistent across GitHub issues. Uses absolute path, potentially git root in some memory contexts but CWD for projects/. To find your project's dir: ls ~/.claude/projects/ and match the encoded form of your pwd.
Citations:
- 1: https://code.claude.com/docs/en/claude-directory
- 2: Project config tied to directory path breaks on file reorganization anthropics/claude-code#24789
- 3: [BUG] Project path encoding collision: different paths can map to the same project directory anthropics/claude-code#35162
- 4: [BUG] Lossy project directory path encoding makes decoding impossible anthropics/claude-code#9221
- 5: https://github.com/kodrunhq/claudefy/blob/main/docs/path-mapping.md
- 6: Path encoding collision causes session loss on directory reorganization anthropics/claude-code#21085
- 7: docs: document project directory path mapping behavior anthropics/claude-code#29496
- 8: https://github.com/liuup/claude-code-analysis/blob/main/src/memdir/paths.ts
- 9: https://github.com/liuup/claude-code-analysis/blob/main/src/tools/AgentTool/agentMemory.ts
Encoding rule may not match Claude Code's actual behavior — dots should be verified.
The implementation maps both / and . to -, but based on publicly available Claude Code documentation and issue discussions, the actual encoding replaces only /, spaces, and underscores with -. If Claude Code preserves dots (e.g., /a.b/c encodes to -a.b-c rather than -a-b-c), HasSession will miss sessions for paths containing dots.
Before merging, confirm the exact encoding rule by inspecting a real ~/.claude/projects/ directory on macOS/Linux with Claude Code installed, or consult the official Claude Code source. If dots are actually preserved, update EncodeProjectPath to exclude them from replacement.
Consider also adding a fallback check: if no session is found after encoding, scan ~/.claude/projects/ for any .jsonl files with a cwd field matching absPath (out of scope for this PR but worth noting for robustness).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/worktree/has_session.go` around lines 9 - 15, The EncodeProjectPath
currently replaces both '/' and '.' with '-', which may be incorrect; inspect
Claude Code's actual encoding (check a real ~/.claude/projects/ or the upstream
source) and if dots are preserved, change EncodeProjectPath to only replace '/'
(and if documented, spaces and underscores) with '-' instead of replacing '.';
update any callers/tests (e.g., HasSession) to use the revised EncodeProjectPath
and add a unit test that verifies encoding for inputs with dots (and consider a
later fallback scan of ~/.claude/projects/ for matching cwd if no session is
found).
- 既存 worktree 内からは --worktree を渡さない LaunchInWorktree を追加し runResume が利用 (name 衝突回避) - Continue 後の自動 fallback を worktree.HasSession() 再チェックでガードし、Ctrl+C や transient エラーで勝手に新セッションが立ち上がらないように - Claude Code 最低バージョン 2.1.118 → 2.1.76 (-n は 2.1.76 で導入) - plan 擬似コードを実装と一致 EncodeProjectPath の Critical 指摘 2 件は実機 (~/.claude/projects/) 検証で false alarm を確認: dot は実際に - に置換される (例 cwd /Users/.../.dotfiles/.claude/... → -Users-...--dotfiles--claude-...)。コードは正しく現状維持。
Summary
claude --worktree <name> -n <name>を呼び出し、worktree 名 = Claude Code セッション名を 1:1 で揃える~/.claude/projects/<encoded-cwd>/*.jsonlの有無で判定し、デフォルトでclaude --continueを呼んで会話を復元する。セッション無しなら-n <name>で新規起動--resume警告を撤去し、RESUME/NEW バッジ表・命名規約・最低バージョン (>= 2.1.118) を追記Spec / Plan
docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.mddocs/superpowers/plans/2026-04-25-worktree-resume-integration.mdTest plan
go test ./...(164 件 / 14 パッケージ pass を確認済み)go build ./...(clean)tests/resume_flow_test.goでccw -nが--worktreeと-nに同一名を渡すことを fake claude バイナリで検証ccw起動 → picker で 💬 RESUME 行を選択 → 過去会話が復元されることを確認ccw起動 → picker で ⚡ NEW 行を選択 →-n <worktree名>で新規セッションが立ち上がることを確認ccw -n→~/.claude/projects/配下にエンコードされた cwd ディレクトリが作られ、.jsonlが記録されることを確認🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
claude --continuewhen a previous session existsDocumentation
Tests