Skip to content

Commit 3437de3

Browse files
authored
fix(ce-worktree): replace bundled-script creator with a portable isolation guardrail (#948)
1 parent 5e6ecca commit 3437de3

9 files changed

Lines changed: 200 additions & 465 deletions

File tree

docs/skills/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Invoked when a specific need arises — not part of any chain.
8585
| [`/ce-commit`](./ce-commit.md) | Create a single, well-crafted git commit — convention-aware, sensitive-file-safe, file-level logical splitting |
8686
| [`/ce-commit-push-pr`](./ce-commit-push-pr.md) | Go from working changes to an open PR with adaptive descriptions — three modes (full workflow / description update / description-only generation) |
8787
| [`/ce-clean-gone-branches`](./ce-clean-gone-branches.md) | Delete local branches whose remote tracking branch is gone, including any associated worktrees |
88-
| [`/ce-worktree`](./ce-worktree.md) | Create a git worktree at `.worktrees/<branch>` with `.env` copying, branch-aware dev-tool trust, and gitignore management |
88+
| [`/ce-worktree`](./ce-worktree.md) | Ensure work happens in an isolated git worktree — detect existing isolation, prefer the harness's native worktree tool, fall back to plain git |
8989

9090
---
9191

docs/skills/ce-worktree.md

Lines changed: 46 additions & 115 deletions
Large diffs are not rendered by default.

plugins/compound-engineering/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ The primary entry points for engineering work, invoked as slash commands. Detail
5050
| [`ce-clean-gone-branches`](../../docs/skills/ce-clean-gone-branches.md) | Clean up local branches whose remote tracking branch is gone |
5151
| [`ce-commit`](../../docs/skills/ce-commit.md) | Create a git commit with a value-communicating message |
5252
| [`ce-commit-push-pr`](../../docs/skills/ce-commit-push-pr.md) | Commit, push, and open a PR with an adaptive description; also update an existing PR description, or generate a description on its own without committing |
53-
| [`ce-worktree`](../../docs/skills/ce-worktree.md) | Manage Git worktrees for parallel development |
53+
| [`ce-worktree`](../../docs/skills/ce-worktree.md) | Ensure work happens in an isolated git worktree — detect existing isolation, prefer native worktree tooling, else create one |
5454

5555
### Workflow Utilities
5656

plugins/compound-engineering/skills/ce-work-beta/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ Determine how to proceed based on what was provided in `<input_document>`.
151151
**Option B: Use a worktree (recommended for parallel development)**
152152
```bash
153153
skill: ce-worktree
154-
# The skill will create a new branch from the default branch in an isolated worktree
154+
# Ensures isolation: detects an existing worktree, prefers the harness's
155+
# native worktree tool, else creates one from the default branch
155156
```
156157

157158
**Option C: Continue on the default branch**

plugins/compound-engineering/skills/ce-work/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ Determine how to proceed based on what was provided in `<input_document>`.
100100
**Option B: Use a worktree (recommended for parallel development)**
101101
```bash
102102
skill: ce-worktree
103-
# The skill will create a new branch from the default branch in an isolated worktree
103+
# Ensures isolation: detects an existing worktree, prefers the harness's
104+
# native worktree tool, else creates one from the default branch
104105
```
105106

106107
**Option C: Continue on the default branch**
Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,77 @@
11
---
22
name: ce-worktree
3-
description: Create an isolated git worktree for parallel feature work or PR review. Use when starting work that should not disturb the current checkout, or when `ce-work` or `ce-code-review` offers a worktree option.
4-
allowed-tools: Bash(bash *worktree-manager.sh)
3+
description: Ensure work happens in an isolated git worktree without disturbing the current checkout. Use when starting work that should stay isolated, or when `ce-work` or `ce-code-review` offers a worktree option. Detects existing isolation first, prefers the harness's native worktree tool, and falls back to plain git.
54
---
65

7-
# Worktree Creation
6+
# Worktree Isolation
87

9-
Create a worktree under `.worktrees/<branch>` with branch-specific setup that `git worktree add` alone does not handle:
8+
Ensure the current work happens in an isolated workspace, without disturbing the user's main checkout. Most coding harnesses now create a worktree by default at session start, so the common case is that **isolation already exists** — detect that first and do not create a redundant one.
109

11-
- Copies `.env`, `.env.local`, `.env.test`, etc. from the main repo (skips `.env.example`)
12-
- Trusts `mise`/`direnv` configs, with branch-aware safety rules so review branches do not auto-grant trust to untrusted `.envrc` content
13-
- Adds `.worktrees` to `.gitignore` if not already ignored
14-
- Does not modify the main repo checkout — `from-branch` is fetched, not checked out
10+
Order of operations: **detect existing isolation -> prefer a native worktree tool -> fall back to plain git.** Never create a worktree the harness cannot see.
1511

16-
## Creating a worktree
12+
## Step 0: Detect existing isolation
1713

18-
Invoke the bundled script via the runtime Bash tool. On Claude Code, `${CLAUDE_SKILL_DIR}` resolves to the skill's own directory across both marketplace-cached installs and `claude --plugin-dir` local development; the runtime Bash tool's CWD is the user's project, not the skill directory, so a bare `bash scripts/worktree-manager.sh` fails. The `:-.` fallback keeps the command syntactically valid where `${CLAUDE_SKILL_DIR}` is unset.
14+
Before creating anything, check whether the current directory is already a linked worktree. Compare the **resolved absolute** git dir against the **resolved absolute** common git dir — resolve each to an absolute path first and compare those, not the raw `git rev-parse` output. Git mixes absolute and relative forms depending on the current directory (from a subdirectory of a normal checkout, `--git-dir` comes back absolute while `--git-common-dir` may be relative), so a raw string compare yields a false "already isolated":
1915

2016
```bash
21-
bash "${CLAUDE_SKILL_DIR:-.}/scripts/worktree-manager.sh" create <branch-name> [from-branch]
17+
git rev-parse --absolute-git-dir # absolute git dir for this worktree
18+
(cd "$(git rev-parse --git-common-dir)" && pwd -P) # absolute shared (common) git dir
2219
```
2320

24-
Defaults:
25-
- `from-branch` defaults to origin's default branch (or `main` if that cannot be resolved)
26-
- The new branch is created at `origin/<from-branch>` (or the local ref if the remote is unavailable)
21+
If the two absolute paths are **equal**, this is a normal checkout — continue to Step 1.
22+
23+
If they **differ**, you are in a linked worktree *or* a submodule. Distinguish them:
2724

28-
Examples:
2925
```bash
30-
bash "${CLAUDE_SKILL_DIR:-.}/scripts/worktree-manager.sh" create feat/login
31-
bash "${CLAUDE_SKILL_DIR:-.}/scripts/worktree-manager.sh" create fix/email-validation develop
26+
git rev-parse --show-superproject-working-tree
3227
```
3328

34-
After creation, switch to the worktree with `cd .worktrees/<branch-name>`.
29+
- **Non-empty** output -> you are in a submodule; treat it as a normal checkout and continue to Step 1.
30+
- **Empty** output -> you are **already in an isolated worktree**. Report the worktree path (`git rev-parse --show-toplevel`) and current branch, and **work in place**. Do not create another worktree — a worktree-from-worktree lands in the wrong tree and is invisible to the harness that made the current one.
31+
32+
## Step 1: Prefer the harness's native worktree tool
33+
34+
If the harness provides a native worktree primitive — for example an `EnterWorktree` / `WorktreeCreate` tool, a `/worktree` command, or a `--worktree` flag — use it and stop. Native tools place, track, and clean up the worktree so the harness can manage it. A behind-the-back `git worktree add` creates phantom state the harness cannot see, navigate to, or clean up.
35+
36+
## Step 2: Git fallback
37+
38+
Only when there is no native tool **and** Step 0 found no existing isolation.
39+
40+
1. **Run from the repo root.** The `.worktrees/` and `.gitignore` paths below are repo-root-relative, but the skill runs from the user's current directory, which may be a subdirectory — so move to the root first: `cd "$(git rev-parse --show-toplevel)"`. Without this, `.worktrees/<branch>` and the `.gitignore` edit would land in the subdirectory (e.g. `src/.worktrees/...`, `src/.gitignore`) instead of at the repo root.
41+
2. Choose a meaningful branch name from the work description (e.g. `feat/login`, `fix/email-validation`) — avoid opaque auto-generated names. Pick a base branch (default: origin's default branch, else `main`).
42+
3. **Ensure `.worktrees/` is gitignored before creating anything**, so worktree contents are never committed: check `git check-ignore -q .worktrees/`**with the trailing slash**, so an existing directory-only `.worktrees/` rule is honored even before the directory exists (`git check-ignore .worktrees` without the slash would miss it and dirty a correctly-configured repo). If it is not ignored, add a `.worktrees/` line to `.gitignore`.
43+
4. Best-effort refresh the base branch without disturbing the current checkout: `git fetch origin <from-branch>`. This is **non-fatal** — if it errors (no `origin` remote, a differently-named remote, or a local-only branch), do not abort; continue to the next step and use the local ref.
44+
5. Create the worktree from the remote base when available, else the local ref: `git worktree add -b <branch-name> .worktrees/<branch-name> origin/<from-branch>`. If `origin/<from-branch>` does not exist, use the local `<from-branch>` ref instead.
45+
6. Switch into it: `cd .worktrees/<branch-name>`.
46+
47+
If `git worktree add` fails with a sandbox or permission error, the requested isolation could not be created. This needs a **blocking** user decision before touching the current checkout — do not silently continue there (the user chose isolation specifically to avoid it, especially when `ce-work` / `ce-code-review` routed here for the worktree option). Report the failure and ask via the platform's blocking question tool: `AskUserQuestion` in Claude Code (call `ToolSearch` with `select:AskUserQuestion` first if its schema isn't loaded), `request_user_input` in Codex, `ask_user` in Gemini, `ask_user` in Pi (via the `pi-ask-user` extension) — offering options such as "work in the current checkout" vs "stop and resolve the permission issue". If no blocking tool exists in the harness or the call errors, present the numbered options in chat and wait for the reply; never skip the confirmation. Only work in the current checkout on explicit confirmation, and do not retry alternative paths automatically.
3548

3649
## Other worktree operations
3750

38-
Use `git` directly — no wrapper is needed and none is provided:
51+
Use `git` directly — no wrapper is needed:
3952

4053
```bash
4154
git worktree list # list worktrees
4255
git worktree remove .worktrees/<branch> # remove a worktree
4356
cd .worktrees/<branch> # switch to a worktree
44-
cd "$(git rev-parse --show-toplevel)" # return to main checkout
45-
```
46-
47-
To copy `.env*` files into an existing worktree created without them, run this from the main repo (not from inside the worktree, since branch names often contain slashes like `feat/login`):
48-
```bash
49-
cp .env* .worktrees/<branch>/
57+
cd "$(git rev-parse --show-toplevel)" # return to the current checkout root
5058
```
5159

52-
## Dev tool trust behavior
53-
54-
When mise or direnv configs are present, the script attempts to trust them so hooks and scripts do not block on interactive prompts. Trust is baseline-checked against a reference branch:
55-
56-
- **Trusted base branches** (`main`, `develop`, `dev`, `trunk`, `staging`, `release/*`): the new worktree's configs are compared against that branch; unchanged configs are auto-trusted. `direnv allow` is permitted.
57-
- **Other branches** (feature branches, PR review branches): configs are compared against the default branch; `direnv allow` is skipped regardless, because `.envrc` can source files that direnv does not validate.
58-
59-
Modified configs are never auto-trusted. The script prints the manual trust command to run after review.
60-
6160
## When to create a worktree
6261

63-
Create a worktree when:
64-
- Reviewing a PR while keeping the main checkout free for other work
62+
Create one (Step 1/2) only when you are **not** already isolated and you need a separate workspace:
63+
64+
- Reviewing a PR while keeping the current checkout free for other work
6565
- Running multiple features in parallel without branch-switching overhead
66-
- Keeping the default branch free of in-progress state
6766

68-
Do not create a worktree for single-task work that can happen on a branch in the main checkout.
67+
Do not create a worktree for single-task work that can happen on a branch in the current checkout — and never when Step 0 shows you are already in one.
6968

7069
## Integration
7170

72-
`ce-work` and `ce-code-review` offer this skill as an option. When the user selects "worktree" in those flows, invoke `bash "${CLAUDE_SKILL_DIR:-.}/scripts/worktree-manager.sh" create <branch>` with a meaningful branch name derived from the work description (e.g., `feat/crowd-sniff`, `fix/email-validation`). Avoid auto-generated names like `worktree-jolly-beaming-raven` that obscure the work.
71+
`ce-work` and `ce-code-review` offer this skill as an option. When the user selects "worktree" in those flows, run Step 0 first: if the work is already isolated, proceed in place; otherwise create one (native tool preferred) with a meaningful branch name derived from the work description.
7372

7473
## Troubleshooting
7574

76-
**"Worktree already exists"**: the path is already in use. Either switch to it (`cd .worktrees/<branch>`) or remove it (`git worktree remove .worktrees/<branch>`) before recreating.
75+
**"Worktree already exists"**: the path is in use. Switch to it (`cd .worktrees/<branch>`) or remove it (`git worktree remove .worktrees/<branch>`) before recreating.
7776

7877
**"Cannot remove worktree: it is the current worktree"**: `cd` out of the worktree first, then `git worktree remove`.
79-
80-
**Dev tool trust was skipped**: the script prints the manual command. Review the config diff (`git diff <base-ref> -- .envrc`), then run the printed command from the worktree directory.

0 commit comments

Comments
 (0)