Skip to content

feat: worktree-resume integration (worktree 名 = session 名 + RESUME/NEW picker)#43

Merged
tqer39 merged 17 commits into
mainfrom
worktree-whimsical-baking-diffie
Apr 25, 2026
Merged

feat: worktree-resume integration (worktree 名 = session 名 + RESUME/NEW picker)#43
tqer39 merged 17 commits into
mainfrom
worktree-whimsical-baking-diffie

Conversation

@tqer39

@tqer39 tqer39 commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Summary

  • 新規 worktree 作成時に claude --worktree <name> -n <name> を呼び出し、worktree 名 = Claude Code セッション名を 1:1 で揃える
  • picker から既存 worktree を選んだ際は ~/.claude/projects/<encoded-cwd>/*.jsonl の有無で判定し、デフォルトで claude --continue を呼んで会話を復元する。セッション無しなら -n <name> で新規起動
  • picker レイアウトを 4 行表示(header / branch / pr / path)に拡張し、行頭に 💬 RESUME / ⚡ NEW バッジを表示。footer にランダム TIPS を表示
  • README から旧 --resume 警告を撤去し、RESUME/NEW バッジ表・命名規約・最低バージョン (>= 2.1.118) を追記

Spec / Plan

  • spec: docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.md
  • plan: docs/superpowers/plans/2026-04-25-worktree-resume-integration.md

Test plan

  • go test ./...(164 件 / 14 パッケージ pass を確認済み)
  • go build ./...(clean)
  • e2e: tests/resume_flow_test.goccw -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

    • Worktrees now support session resumption via claude --continue when a previous session exists
    • Auto-generated names for new worktree sessions
    • Visual RESUME/NEW status badges in the UI
    • Random footer tips for user guidance
  • Documentation

    • Updated minimum Claude version requirement to >= 2.1.118
    • Added session naming conventions and continuity documentation
  • Tests

    • Integrated tests for session resume workflow

tqer39 and others added 16 commits April 25, 2026 16:59
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>
@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@tqer39 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 43 minutes and 53 seconds before requesting another review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6395cf73-a913-4a08-bd31-14b24262a594

📥 Commits

Reviewing files that changed from the base of the PR and between ba23913 and a7b3598.

📒 Files selected for processing (6)
  • README.md
  • cmd/ccw/main.go
  • docs/README.ja.md
  • docs/superpowers/plans/2026-04-25-worktree-resume-integration.md
  • internal/claude/claude.go
  • internal/claude/claude_test.go
📝 Walkthrough

Walkthrough

This 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 claude --continue to resume existing sessions or claude -n <name> to start fresh, and updates the picker UI with resume/new badges, footer tips, and expanded row formatting. Documentation is updated and integration tests are added.

Changes

Cohort / File(s) Summary
Documentation Updates
README.md, docs/README.ja.md
Updated worktree-launch behavior to reflect session-aware flow using claude --continue for existing sessions and claude -n <name> for new ones; added session badge legend and naming convention section; raised minimum Claude Code version requirement to >= 2.1.118.
Implementation & Design Plans
docs/superpowers/plans/2026-04-25-worktree-resume-integration.md, docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.md
Added new implementation plan and design specification documents detailing 1:1 worktree-to-session mapping, session detection logic, picker UI changes with resume/new badges, code-level adjustments, error handling, and comprehensive test/validation checklist.
Name Generation
internal/namegen/namegen.go, internal/namegen/namegen_test.go
New package for deterministic name generation producing adjective-noun-4hex format with seeding via time and atomic counter to prevent collisions on rapid calls; full test coverage validates pattern, uniqueness, and determinism.
Session Detection
internal/worktree/has_session.go, internal/worktree/has_session_test.go
New helper functions to encode worktree paths and detect Claude Code sessions by checking for .jsonl files in ~/.claude/projects/<encoded-path>/; handles missing HOME and absent directories gracefully.
Footer Tips System
internal/tips/tips.go, internal/tips/tips_test.go
New package providing a curated set of rotating footer tip strings with deterministic random selection via seed-based PCG generator; includes Defaults() and PickRandom() exports.
Claude CLI Integration
internal/claude/claude.go, internal/claude/claude_test.go
Replaced Resume function with Continue; updated LaunchNew to accept a name parameter and construct --worktree <name> -n <name> arguments; added BuildContinueArgs to append --continue flag; updated tests to reflect new signatures.
Worktree Metadata
internal/worktree/worktree.go, internal/worktree/worktree_test.go
Extended Info struct with HasSession boolean field; populated during worktree enumeration; integration test verifies correct session detection across worktree list.
Picker UI Enhancements
internal/picker/delegate.go, internal/picker/delegate_test.go
Converted row layout from 2-line to 4-line format (header with resume/new badge + branch + PR + path); added ResumeBadge() styling function for colored/uncolored badge rendering; refactored test expectations to cover new badge and multiline layout.
Picker State & Display
internal/picker/model.go, internal/picker/update.go, internal/picker/run.go
Added tip field to Model initialized via tips.PickRandom(); added HasSession field to Selection struct; propagated HasSession through currentSelection() and runFallback().
Picker Footer & Styling
internal/picker/style.go, internal/picker/style_test.go, internal/picker/view.go, internal/picker/view_test.go
New ResumeBadge() function renders [RESUME] or [NEW] with appropriate colors/padding; footer rendering updated to show either tip or gh install hint based on state; new tests validate footer behavior and badge styling.
CLI Command Integration
cmd/ccw/main.go
Added runResume flow for session-aware launch decisions; new worktreeName helper extracts trailing path segment; --new-worktree and picker "new" action both call namegen.Generate() and pass generated name to claude.LaunchNew; resume path uses claude.Continue with fallback to claude.LaunchNew.
Test Infrastructure
tests/fakes/fake_claude/main.go, tests/resume_flow_test.go
New fake Claude binary for deterministic testing that logs arguments and exit codes via environment variables; integration test TestResumeFlow_NewWorktreePassesNameToBoth verifies --worktree and -n flags are passed correctly with matching name values.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A worktree hops through time so fast,
With names that stick through present and past,
Resume or fresh—the session knows,
While tips dance by as the picker grows! 💬✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main feature addition: worktree-resume integration with 1:1 name mapping and picker UI badges for session state detection.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-whimsical-baking-diffie

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

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

truncateToWidth can 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 cause lipgloss.Width to misreport width or render replacement characters. The function is also O(n²) due to repeated lipgloss.Width calls. 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_COLOR output; consider noting that the colored variant also pads NEW with 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 that tip is 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, but TestResumeBadge_Colored only checks for the literal label substring. Since ResumeBadge returns the plain [RESUME] / [NEW] string under NO_COLOR, a substring match alone won't actually distinguish colored output from a misconfigured no-color path. Consider adding a strings.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.

setupFakeEnv rebuilds both fake_claude and the full ccw binary on every invocation, so this test always runs (and rebuilds) under a plain go test ./.... For a fast inner dev loop you may want a //go:build integration tag on this file (or a -short skip), 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 rendered pr: 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 duplicate worktreeName helpers using path.Base().

Both cmd/ccw/main.go:267 and internal/picker/delegate.go:88 define identical worktreeName functions that extract the last path component—they're hand-rolled equivalents of stdlib's path.Base(). Since paths come from git worktree list --porcelain and don't have trailing slashes, using path.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's exec.Cmd.Run() does not surface "immediate vs delayed" — only the final exit code. As written, any non-zero exit from a long, fully-interactive --continue session (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-coded README.ja.md:79-80 line references.

The spec references README.ja.md:79-80 in four places as the location of the old --resume warning. 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 empty name.

BuildNewArgs unconditionally injects --worktree <name> -n <name>. If a caller ever passes name == "" (e.g., a future code path where namegen.Generate() returns empty under some failure mode, or a refactor that forgets to populate it), the resulting argv becomes --worktree "" -n "", which claude will 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

📥 Commits

Reviewing files that changed from the base of the PR and between e915770 and ba23913.

📒 Files selected for processing (26)
  • README.md
  • cmd/ccw/main.go
  • docs/README.ja.md
  • docs/superpowers/plans/2026-04-25-worktree-resume-integration.md
  • docs/superpowers/specs/2026-04-25-worktree-resume-integration-design.md
  • internal/claude/claude.go
  • internal/claude/claude_test.go
  • internal/namegen/namegen.go
  • internal/namegen/namegen_test.go
  • internal/picker/delegate.go
  • internal/picker/delegate_test.go
  • internal/picker/model.go
  • internal/picker/run.go
  • internal/picker/style.go
  • internal/picker/style_test.go
  • internal/picker/update.go
  • internal/picker/view.go
  • internal/picker/view_test.go
  • internal/tips/tips.go
  • internal/tips/tips_test.go
  • internal/worktree/has_session.go
  • internal/worktree/has_session_test.go
  • internal/worktree/worktree.go
  • internal/worktree/worktree_test.go
  • tests/fakes/fake_claude/main.go
  • tests/resume_flow_test.go

Comment thread cmd/ccw/main.go
Comment thread cmd/ccw/main.go
Comment thread docs/README.ja.md Outdated
Comment thread docs/superpowers/plans/2026-04-25-worktree-resume-integration.md
Comment on lines +57 to +69
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 に出ないので影響なし

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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:


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.

Comment on lines +9 to +15
// 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)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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=md

Repository: 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:


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-...)。コードは正しく現状維持。
@tqer39 tqer39 merged commit efa112f into main Apr 25, 2026
8 checks passed
@tqer39 tqer39 deleted the worktree-whimsical-baking-diffie branch April 25, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant