# 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"
```