Never call git worktree add directly -- always use the worktree-manager.sh script.
The script handles critical setup that raw git commands don't:
.env, .env.local, .env.test, etc. from main repo.worktrees is in .gitignorepackage.json → npm install, composer.json → composer install, pyproject.toml → pip install -e ., go.mod → go mod downloadBefore creating a worktree, verify the worktree directory is gitignored:
# Verify .worktrees is ignored (should output ".worktrees")
git check-ignore .worktrees || echo "WARNING: .worktrees not in .gitignore"
If not ignored, add it to .gitignore before proceeding. The manager script handles this, but verify when troubleshooting.
This subsection documents the safety logic worktree-manager.sh create implements when branching from the default branch — it is not a manual sequence to run. The script handles the steps; the rationale lives here so users can reason about the behavior.
When creating a worktree's branch from the default branch (main/master), the local base may be ahead of origin/ due to another session, worktree, or background task. Branching from local HEAD silently carries those unrelated commits into the new feature branch and the eventual PR — and an unconditional git reset --hard origin/ would silently drop the user's intentional unpushed work.
The script's safe sequence:
git fetch --no-tags origin <base>
unpushed=$(git log "origin/$base..$base" --oneline)
if [ -n "$unpushed" ]; then
# Surface the commit list and prompt: carry forward (base_ref=$base) or leave on local <base> (base_ref=origin/$base)
...
fi
git worktree add .worktrees/<name> -b <branch> "$base_ref"
Two failure modes the prompt distinguishes:
past origin/ with unrelated commits. The user wants origin/ as the new branch's base; the unpushed commits stay on local for separate handling. intending them for a feature branch. The user wants HEAD as the new branch's base so the commits carry forward.Local git state alone cannot distinguish these (author email is unreliable in multi-session setups), so the script surfaces the choice rather than guessing. Default fallback when the user can't be reached: branch from origin/ and report the unpushed commits in the change summary so the user resolves them deliberately. If the script does not implement this yet, that is a known gap — open an issue rather than working around with raw git worktree add.
After creating a worktree, run the project's test suite to establish a clean baseline. Pre-existing failures in the worktree should be caught before starting new work -- not discovered mid-implementation.
# CORRECT - Always use the script
bash ${CLAUDE_PLUGIN_ROOT}/skills/ia-git-worktree/scripts/worktree-manager.sh create feature-name
# WRONG - Never do this directly
git worktree add .worktrees/feature-name -b feature-name main
| Command | Description | Example |
|---|---|---|
| --------- | ------------- | --------- |
create | Create worktree + branch (default: from main) | ...worktree-manager.sh create feature-login |
list / ls | List all worktrees with status | ...worktree-manager.sh list |
switch / go | Switch to existing worktree | ...worktree-manager.sh switch feature-login |
copy-env | Copy .env files to existing worktree | ...worktree-manager.sh copy-env feature-login |
cleanup / clean | Interactively remove inactive worktrees | ...worktree-manager.sh cleanup |
After cleanup, run git worktree prune to remove any orphaned worktree metadata from manually deleted directories.
All commands use: bash ${CLAUDE_PLUGIN_ROOT}/skills/ia-git-worktree/scripts/worktree-manager.sh
Before creating worktrees, detect the execution context:
git rev-parse --show-toplevel against git worktree list. If the current directory is already a linked worktree, skip creation -- work directly in the existing worktree.$CODEX_SANDBOX is set or the repo is at a non-standard path (e.g., /tmp/, /workspace/), worktrees may not be supported. Fall back to regular branch switching.git rev-parse --is-bare-repository returns true, worktrees are the only way to have a working directory. Adjust paths accordingly.Adapt the workflow to the detected context rather than failing with a generic error.
/ia-review/ia-workAlways offer choice:
When work in a worktree is done, verify tests pass, then present exactly 4 options:
gh pr create, keep worktree until mergedClean up the worktree directory only for options 1 and 4. For option 2, the worktree stays until the PR merges.
When completing work in a worktree (before merge or PR), output a structured summary:
CHANGES MADE:
- src/routes/tasks.ts: Added validation middleware
THINGS I DIDN'T TOUCH (intentionally):
- src/routes/auth.ts: Has similar validation gap but out of scope
POTENTIAL CONCERNS:
- The Zod schema is strict -- rejects extra fields. Confirm this is desired.
The "DIDN'T TOUCH" section prevents reviewers from wondering whether adjacent issues were missed or intentionally deferred.
Installing hooks into .git/hooks/ silently fails on any repo that uses Husky. Husky sets core.hooksPath (typically to .husky/_) and git ignores .git/hooks/ entirely when that config is non-empty. The hook file lands on disk, is executable, is correct, and is dead. Invisible failure until someone asks why the post-merge behavior isn't running.
hooks_path=$(git -C "$repo" config --get core.hooksPath)
$(git rev-parse --git-common-dir)/hooks/ as usual..husky/_ (or any path containing husky.sh / h trampoline): Husky v9 setup. Write to .husky/; do NOT include the v8-era . "$(dirname "$0")/_/husky.sh" line (v9 prints a deprecation warning if you do)..git/hooks/ lives in the common git dir and runs for every worktree of the clone. A hook installed once fires across all trees. Two rules to stay safe:
git rev-parse --show-toplevel, not a hardcoded path. Hardcoding means the last install-hooks invocation wins for every worktree.#!/bin/sh
root="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
[ -d "$root/.my-tool" ] || exit 0
( cd "$root" && my-tool index ) >>"$root/.my-tool/hook.log" 2>&1 &
exit 0
Redirect to a log file inside the tool's state dir, not /dev/null — silent failures produce stale state you only notice hours later.
Tooling artifacts (local index dirs, hook helpers, per-developer scratch files) belong in .git/info/exclude, NOT in the tracked .gitignore.
.gitignore is content — tracked, shared with the team, reviewed in PRs. Adding a personal tooling rule there pollutes a shared file. On foreign repos (upstream projects, third-party clones) the rule either rides into a PR by accident or sits as a dirty working tree forever..git/info/exclude is local — untracked, lives in the common git dir, shared across every worktree of the clone. Same syntax and semantics as .gitignore without the leakage.Don't hardcode $repo/.git/info/exclude. In a worktree, .git is a file (gitlink), not a directory. Use git itself:
exclude=$(git -C "$repo" rev-parse --git-path info/exclude)
line="/.my-tool/"
mkdir -p "$(dirname "$exclude")"
grep -qxF "$line" "$exclude" 2>/dev/null || printf '\n# my-tool\n%s\n' "$line" >> "$exclude"
grep -qxF matches the exact line with no regex surprises.
Only when the artifact is genuinely team-shared and belongs in the repo (build outputs used in CI, generated files, vendored dependencies). If in doubt, ask: "would another contributor benefit from this rule?" If no, exclude locally.
git worktree list shows the new entry.worktrees directory confirmed in .gitignore共 1 个版本