# Claude Focus Workflow Automated workflow that creates an isolated git worktree, tmux session, and Claude Code instance per task. Each issue gets its own branch, directory, and AI session — fully isolated from other work. Config files: `~/.zsh/aliases/claude.zsh`, `~/.local/bin/claude-review` > [!info] Full Specification > For implementation details, all algorithms (session ID generation, merge detection, reconnect backoff), provider plugin architecture, and a complete env-var reference suitable for reimplementing `cf` in another language, see [docs/cf/README.md](https://github.com/kakkoyun/dotfiles/blob/main/docs/cf/README.md) in the dotfiles repo. > [!info] Mental Model > One task = one worktree = one branch = one tmux session = one Claude session. The session ID is deterministic (uuid5 of `repo/branch`), so reconnecting to the same branch always resumes the same Claude conversation. ## Quick Reference | Alias | Command | Purpose | |-------|---------|---------| | `cf` | `claude-focus` | Create worktree + tmux + Claude Code | | `cf --remote` | `claude-focus --remote` | Create remote workspace (DD Workspaces or exe.dev) + SSH + Claude Code | | `cf --remote --yolo` | `claude-focus --remote --yolo` | Same as `--remote` but passes `--dangerously-skip-permissions` to Claude Code | | `cfd` | `claude-focus-done` | Tear down worktree/remote workspace + session + branch | | `cfl` | `claude-focus-list` | List active focus sessions (local + remote) | | `cfr` | `claude-focus-resume` | Re-attach to a focus session; reconnects dropped SSH for remote | | `cfgc` | `claude-focus-gc` | Garbage-collect merged/closed sessions | | `cfe` | `cf-open-editor --visual` | Open session's codebase in VS Code / Zed | | `cfed` | `cf-open-editor --terminal` | Open session's codebase in `$EDITOR` (nvim) | | `csb` | `claude-session-branch` | Claude session tied to current branch | ## Setup The workflow is defined in two files: - **`~/.zsh/aliases/claude.zsh`** — all shell functions and aliases - **`~/.local/bin/claude-review`** — standalone review script for neovim Both are sourced/available automatically via `~/.zshrc`. ### Requirements - `git` with worktree support (2.5+) - `tmux` (any recent version) - `python3` (for uuid5 generation) - `gh` + `jq` (for `--from-pr` flag) - `nvim` (for review panes) - `fzf` (optional, for session picker in `cfr`) ## Usage ### Start a Focus Session ```bash # From any git repository — always forks from main/master/trunk cf issue-42 # named session cf # auto-named: <repo>-<timestamp> # Fork from a specific branch cf --current fix # fork from whichever branch is currently checked out cf --from develop x # fork from "develop" explicitly # Create worktree from an existing PR (mirrors native claude --from-pr) cf --from-pr 42 # fetches PR #42 branch + base, starts claude --from-pr 42 # Create a remote workspace (Datadog Workspaces or exe.dev) instead of a local worktree cf --remote task-name # remote workspace on main, named "task-name" cf --remote # auto-named: <repo>-remote-<timestamp> cf --remote --from develop fix # remote workspace on the "develop" branch cf --remote --current fix # remote workspace on the current branch cf --remote --yolo task-name # remote workspace with --dangerously-skip-permissions ``` This creates: 1. A git worktree at `~/Workspace/.worktrees/<repo>/<name>/` 2. A new branch forked from `main` (default), current branch (`--current`), a named branch (`--from`), or the PR's base branch (`--from-pr`) 3. A tmux session named after the branch 4. Claude Code running with a deterministic session ID (or `--from-pr` for PR sessions) #### Fork Awareness (upstream remotes) When the repo has an `upstream` remote (i.e., it is a fork), `cf` automatically: - Fetches from `upstream` instead of `origin` to use the source of truth - Prefixes the branch name with `kakkoyun/` (e.g., `cf issue-42` → branch `kakkoyun/issue-42`) - Skips the prefix if the name already starts with `kakkoyun/` #### `--from-pr` details `--from-pr <number>` resolves the PR's head branch and base branch via `gh pr view`, creates the worktree from the PR branch, then starts Claude with `claude --from-pr <number>`. This resumes the session auto-linked to the PR (set when `gh pr create` was run inside a focus session) and pre-loads the PR diff as context. ### Workspace Mode (Non-git Directories) When `cf` is run in a directory that is not a git repository (or git initialization fails), it falls back to **workspace mode** — no worktree or branch is created. Only a tmux session and Claude Code instance are launched, allowing AI-assisted work in any directory. ```bash cd /tmp/scratch cf my-task # workspace mode: no worktree, no branch, just tmux + Claude ``` `CF_WORKSPACE_MODE=1` is stored in the tmux environment. `cfd` still tears down the tmux session. `cfgc` does not scan workspace sessions — use `cfd` for cleanup. > [!note] No worktree or branch management > Workspace mode skips all git operations. Session ID is still deterministic (uuid5 of `repo/name`, where repo is derived from the directory name). ### Resume a Session ```bash cfr issue-42 # by name cfr # fzf picker (if fzf is installed) ``` The fzf picker shows both active tmux sessions and **orphaned worktrees** (worktrees on disk with no matching tmux session, marked `[orphaned]`). See [Resume & Recovery](#resume--recovery) below. ### List Active Sessions ```bash cfl # Output: # issue-42 branch=issue-42 ~/Workspace/.worktrees/myrepo/issue-42/ # feature-auth branch=feature-auth ~/Workspace/.worktrees/myrepo/feature-auth/ ``` ## Resume & Recovery ### Normal Resume ```bash cfr issue-42 # attach by name cfr # fzf picker shows active sessions + orphaned worktrees ``` ### Orphaned Worktree Recovery When a tmux session is destroyed (system restart, `tmux kill-server`) but the worktree still exists on disk, `cfr` detects the orphan and recovers automatically: 1. Scans `~/Workspace/.worktrees/` for worktrees whose derived session name matches 2. Recreates the tmux session with the original `CF_*` metadata 3. Starts `claude --continue` — resumes the most recent Claude session *in that worktree directory* Because each worktree is a unique directory, `--continue` picks up exactly where Claude left off before the tmux session was lost. The fzf picker also shows orphaned worktrees with an `[orphaned]` tag so they're discoverable without knowing the session name: ``` issue-42 (active) feature-auth [orphaned] ``` Selecting an orphaned entry triggers the same recovery path. ## Review ### tmux Keybindings | Keys | Action | Mode | |------|--------|------| | `prefix + R` | Split right with branch review | All changes since diverging from main | | `prefix + E` | Split below with working review | Staged + unstaged + untracked | Both open neovim with changed files. The pane closes when you exit neovim. > [!tip] Prefix Key > The tmux prefix is `Ctrl+Space`. So `Ctrl+Space` then `R` opens a branch review pane. ### Standalone Review Script The `claude-review` script works independently of tmux: ```bash claude-review # working mode (default) claude-review --staged # only staged changes claude-review --branch # all changes since main + uncommitted ``` For 4 or fewer files, neovim opens with vertical splits (`-O`). For more files, it opens a plain buffer list. ## Open in Editor When reviewing code produced by Claude in a remote or local session, `cf-open-editor` opens the codebase in an editor for side-by-side review without leaving the tmux session. ```bash cfe # open in visual editor (VS Code or Zed) cfed # open in terminal editor ($EDITOR, default: nvim) # Or use tmux keybindings (from inside any cf session): # prefix + e → open $EDITOR in a vertical split # prefix + o → open visual editor # Or use Ghostty shortcuts (pass-through to tmux): # Cmd+Shift+E → open $EDITOR in a vertical split # Cmd+Shift+O → open visual editor ``` ### Session type handling | Session Type | `--terminal` (`cfed`) | `--visual` (`cfe`) | |---|---|---| | Local worktree | Split pane, `$EDITOR .` at worktree path | `code`/`zed` opens local path | | Workspace (non-git) | Split pane, `$EDITOR .` at worktree path | `code`/`zed` opens local path | | Bare | Split pane, `$EDITOR .` at worktree path | `code`/`zed` opens local path | | Sandbox local (VirtioFS) | Split pane, `$EDITOR .` at host path | `code`/`zed` opens host path | | Sandbox remote | Error: VSOCK barrier | Error: VSOCK barrier | | Remote (exe.dev / workspaces) | SSH + `$EDITOR .` at remote path | `vscode://` or `zed://` URL | ### Visual editor detection `cf-open-editor --visual` selects the editor in this order: 1. `$CF_VISUAL_EDITOR` env var (explicit override) 2. `code` in `$PATH` (VS Code — recommended; has mature SSH remote editing support) 3. `zed` in `$PATH` (fallback) For remote sessions, VS Code opens via `vscode://vscode-remote/ssh-remote+<host><path>?windowId=_blank` and Zed via `zed://ssh/<host><path>`. > [!tip] Override the editor > Set `CF_VISUAL_EDITOR=zed` to always use Zed regardless of what's in PATH. ## Cleanup ### Single session (`cfd`) ```bash cfd issue-42 # interactive confirmation cfd # current session (when inside tmux) cfd --force issue-42 # skip confirmation + force-delete unmerged branch ``` Removes the tmux session, worktree directory, branch, and empty parent dirs. ### Bulk garbage-collect (`cfgc`) Scans all worktrees under `~/Workspace/.worktrees/` and removes those whose branches are merged or closed. ```bash cfgc # interactive — prompts per session cfgc --dry-run # preview only, nothing touched cfgc --all # clean everything merged/closed without prompts ``` Merge detection (in priority order): 1. **GitHub PR state** via `gh pr view` — handles regular, squash, and rebase merges 2. **Git ancestry** — `git merge-base --is-ancestor` for regular merges 3. **Diff fingerprint** — `cksum` match against commits in `main` (catches squash merges without a PR) ## Native Claude Code Features Claude Code has built-in worktree support that complements (not replaces) `cf`. ### `--worktree` CLI Flag ```bash # Named worktree — creates .claude/worktrees/feature-auth/ with branch worktree-feature-auth claude --worktree feature-auth # Auto-named — generates random name like "bright-running-fox" claude --worktree ``` Worktrees are created at `<repo>/.claude/worktrees/<name>` (project-local). On exit with no changes, the worktree auto-removes. With changes, Claude prompts to keep or remove. **Contrast with `cf`:** `cf` uses `~/Workspace/.worktrees/<repo>/` (global, outside the repo), supports flexible base branches, tmux integration, and deterministic session IDs. The built-in flag is a lighter-weight alternative for one-off sessions without all that scaffolding. ### `EnterWorktree` Tool (In-Session) Available during an active Claude Code session. Triggered by saying "work in a worktree" or programmatically by Claude. - Same creation logic as `--worktree` - Switches the session's working directory to the new worktree - Useful when a task unexpectedly needs isolation mid-conversation ### Subagent Worktree Isolation (`isolation: worktree`) The most powerful built-in feature for parallel agent work: ```yaml --- name: isolated-worker description: Implements features in an isolated worktree copy of the repository isolation: worktree --- ``` Or via the Agent tool: `Agent(subagent_type="...", isolation="worktree")`. **Behavior:** - Each subagent gets its own worktree — a full isolated copy of the repository - Multiple subagents run in parallel without file conflicts - Auto-cleanup: worktree removed if subagent makes no changes - If changes were made: worktree path and branch returned in result **When to use:** parallel implementation tasks where subagents edit files. Read-only agents (research, analysis, review) don't need isolation — skip it to avoid overhead. **When NOT to use:** - Single subagent with no parallel work - Non-git directories (use workspace mode) **Custom subagent definition** (`.claude/agents/isolated-worker.md`): ```markdown --- name: isolated-worker description: Implements features in an isolated worktree copy of the repository isolation: worktree --- You are a feature implementation agent. You work in an isolated worktree to avoid conflicting with other agents or the main session. Follow the project's coding standards. Run tests before reporting completion. ``` **CLAUDE.md rule to add** (project or global): ```markdown ## Subagent Isolation When spawning subagents that edit files, use `isolation: worktree` to prevent file conflicts. This gives each subagent a full copy of the repository via git worktree. - Use `Agent(subagent_type="...", isolation="worktree")` for parallel tasks - Worktrees are created at `<repo>/.claude/worktrees/` and auto-cleaned - If the subagent made changes, the worktree path and branch are returned - The main conversation merges results from subagent branches ``` **Safety considerations:** - Add `.claude/worktrees/` to `.gitignore` for any repo using built-in worktrees - Branch naming: built-in uses `worktree-<name>` prefix — avoid this on feature branches to prevent collisions - Disk space: each worktree is a full checkout; be mindful of parallel subagent count ### Comparison Matrix | Capability | Built-in `--worktree` | claude-focus (`cf`) | Worktree Plugin | |---|---|---|---| | **Layer** | Claude Code CLI/tool | Shell (zsh) | Claude Code skill | | **Worktree location** | `<repo>/.claude/worktrees/` | `~/Workspace/.worktrees/<repo>/` | `.worktrees/` or user-chosen | | **Branch naming** | `worktree-<name>` | `<name>` (matches user input) | `<name>` (matches user input) | | **Base branch** | Default remote branch only | `--current`, `--from`, `--from-pr`, or auto-detect main | Current branch | | **Tmux integration** | None | Full (create, resume, metadata, switch) | None | | **Session determinism** | None (new session each time) | uuid5-based (resume same session) | None | | **PR session resume** | `claude --from-pr <n>` | `cf --from-pr <n>` (wraps native) | N/A | | **Orphan recovery** | None | `cfr` recreates tmux + `claude --continue` | None | | **Cleanup** | Prompt on exit (keep/remove) | `cfd` (interactive) + `cfgc` (GC merged) | Manual | | **GC / merge detection** | None | PR state + ancestry + squash-merge heuristic | None | | **Subagent isolation** | `isolation: worktree` | N/A (shell-level) | N/A | | **Parallel agents** | Yes (multiple subagents) | Yes (multiple tmux sessions) | No | | **Non-git support** | Via WorktreeCreate/Remove hooks | Workspace mode (tmux only) | No | | **Remote compute** | No | `--remote` (DD Workspaces, exe.dev) | No | | **Tab completion** | N/A | Full zsh completion | N/A | ## Layered Architecture The three layers compose without conflict: ``` Shell layer: cf/cfd/cfr/cfgc (tmux + worktree + session management) ├─ local: git worktree at ~/Workspace/.worktrees/<repo>/ │ └─ launches: claude --session-id <uuid5> (or --from-pr <n>) │ └─ uses: EnterWorktree (in-session isolation if needed) │ └─ uses: Agent(isolation: worktree) (subagent parallelism) └─ remote: cloud workspace (DD Workspaces or exe.dev) via --remote └─ launches: ssh -t <host> 'cd ~/src/<repo> && claude' ``` ### For human-driven multi-tasking (shell level) ```bash cf feature-x # creates worktree + tmux + deterministic Claude session cf --from dev fix # fork from develop branch cf --from-pr 42 # resume PR-linked Claude session cfr # resume any focus session (fzf picker, recovers orphans) cfgc # clean up merged worktrees ``` ### For agent-driven parallelism (inside Claude Code) - Subagents use `isolation: worktree` for conflict-free parallel execution - `EnterWorktree` for mid-session isolation when exploring risky changes - Built-in worktrees auto-clean when no changes made ### For in-session worktree setup (plugin) ```bash /worktree:worktree # guided worktree creation with safety checks + test baseline ``` | Layer | Tool | Role | |-------|------|------| | Shell | `cf`/`cfd`/`cfr`/`cfgc` | Human task switching, tmux, session resume, GC | | CLI | `--worktree` | Quick one-off worktree sessions | | In-session | `EnterWorktree` | Mid-conversation isolation | | Subagent | `isolation: worktree` | Parallel agent execution without file conflicts | | Skill | `/worktree:worktree` | Guided setup with safety checks and test baseline | ## Internals ### Directory Structure ``` ~/Workspace/ ├── Sandbox/ │ └── myrepo/ ← main clone (origin) └── .worktrees/ └── myrepo/ ├── issue-42/ ← worktree (branch: issue-42) └── feature-auth/ ← worktree (branch: feature-auth) ``` For forks with `upstream` remote, branches are prefixed: ``` .worktrees/ └── myrepo/ └── kakkoyun/ └── issue-42/ ← worktree (branch: kakkoyun/issue-42) ``` ### Session ID Generation Claude Code session IDs are generated deterministically using uuid5: ``` session_id = uuid5(NAMESPACE_DNS, "<repo-name>/<branch-name>") ``` This means the same repo + branch combination always produces the same session ID. Resuming a worktree reconnects to the exact same Claude conversation. For `--from-pr` sessions, `claude --from-pr` is used instead (resumes the PR-linked session by PR number rather than uuid5). ### tmux Session Name Sanitization Branch names are sanitized for tmux compatibility: `/`, `.`, and `:` are replaced with `--`. | Branch | tmux session name | |--------|------------------| | `issue-42` | `issue-42` | | `kakkoyun/issue-42` | `kakkoyun--issue-42` | | `v1.2.3:hotfix` | `v1--2--3--hotfix` | ### tmux Metadata Each focus session stores metadata as tmux environment variables: | Variable | Example | Purpose | |----------|---------|---------| | `CF_WORKTREE_PATH` | `~/Workspace/.worktrees/myrepo/issue-42/` | Worktree location (local) or `remote:<host>` (remote) | | `CF_BRANCH_NAME` | `issue-42` | Branch name | | `CF_GIT_ROOT` | `~/Workspace/Sandbox/myrepo` | Original repo path | | `CF_SESSION_ID` | `a1b2c3d4-...` | Claude session ID | | `CF_BASE_BRANCH` | `main` | Branch this worktree was forked from | | `CF_REMOTE_PROVIDER` | `workspaces` or `exedev` | Remote provider identifier; absent for local sessions | | `CF_REMOTE_NAME` | `myrepo-remote-20260305-120000` | Workspace name used with the provider CLI | | `CF_REMOTE_HOST` | `workspace-myrepo-remote-20260305-120000` | SSH hostname for the remote workspace | | `CF_REMOTE_WORK_DIR` | `/home/ubuntu/src/myrepo` | Absolute path on the remote host; pre-populated at session creation for `cf-open-editor` | | `CF_YOLO_MODE` | `true` | Set when `--yolo` was passed; preserved by `cfr` on SSH reconnect | | `CF_WORKSPACE_MODE` | `1` | Set in non-git workspace sessions; no worktree or branch is created | Read by `cfd` for cleanup, `cfl` for listing, and `cfgc` for merge-back flows. Used by `cfr` to rebuild session context during orphan recovery. ### Session Branch (csb) The `csb` alias is a simpler variant — it launches Claude Code with a session ID tied to the current branch, without creating a worktree or tmux session. Useful when you want session continuity but not full isolation: ```bash cd ~/Workspace/Sandbox/myrepo git checkout feature-x csb # launches Claude with session tied to "feature-x" ```