Block Worktree Removal With Uncommitted or Unpushed Changes

Removing a worktree deletes its working directory — any uncommitted edits or commits that only exist on a local, unpushed branch there are simply gone. This checks both conditions and blocks removal until the work is either pushed or the human explicitly confirms it's disposable.

Worktree & Git Safety Event: WorktreeRemove Claude Code Open source · Free

1. Save this as the hook script

#!/usr/bin/env python3
"""WorktreeRemove hook — blocks removal if uncommitted or unpushed
changes exist. Any non-zero exit fails removal while the path still
exists."""
import json, subprocess, sys

data = json.load(sys.stdin)
path = data.get("worktree_path", ".")

try:
    status = subprocess.run(["git", "status", "--porcelain"], cwd=path,
                             capture_output=True, text=True, timeout=10).stdout
    unpushed = subprocess.run(["git", "log", "@{u}..", "--oneline"], cwd=path,
                               capture_output=True, text=True, timeout=10)
except Exception:
    sys.exit(0)

if status.strip():
    print(f"BLOCKED: {path} has uncommitted changes.", file=sys.stderr)
    sys.exit(1)

if unpushed.returncode == 0 and unpushed.stdout.strip():
    print(f"BLOCKED: {path} has commits not yet pushed to its upstream.", file=sys.stderr)
    sys.exit(1)

sys.exit(0)

Save to a path inside your project, e.g. .claude/hooks/block-worktree-removal-unpushed-changes.sh, then chmod +x it.

2. Wire it into .claude/settings.json

{
  "hooks": {
    "WorktreeRemove": [
      {
        "hooks": [
          {"type": "command", "command": "python3 .claude/hooks/block-worktree-removal-unpushed-changes.py"}
        ]
      }
    ]
  }
}

Before you use this

A hook runs with your local shell's permissions — review the script above before adding it, the same way you'd review any script you didn't write yourself. See our guide on hook security risks for the specific CVEs this category of feature has already produced.

Related hooks

← All hooks
FEEDBACK