Replacing My Custom Git Worktree Skill with Claude Code Hooks #54
mattbrailsford
announced in
Blog Post
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
slug: replacing-my-custom-git-worktree-skill-with-claude-code-hooks
published: 2026-02-24
If you've been following along with my recent posts, you'll know that git worktrees have become a fundamental part of how I work. One worktree per feature, one AI session per worktree, everything isolated. It's the setup that makes parallel AI-assisted development actually work.
For months, I've been managing this through a custom Claude Code skill —
/git-worktree— that handled creating worktrees, naming branches, and crucially, copying local development files into each new worktree. It worked well. But recently, Claude Code shipped native worktree support, and I found myself in that slightly awkward position of having a custom tool that overlaps with an official feature.The question was whether I could retire my skill and adopt the built-in approach — without losing the bits that made my workflow actually work.
What My Custom Skill Did
The
/git-worktreeskill was straightforward. When I started work on a feature, it would:.worktreesdirectoryfeature/add-streaming,feature/fix-rate-limiting, etc..worktreeincludefile from the main repo into the new worktreeThat third point is the important one. A git worktree is a fresh checkout — it only contains committed files. But in any real project, there are local files that git quite rightly ignores but that you absolutely need to actually run the thing. Config files like
appsettings.Development.jsonand.envwith your API keys and connection strings. Visual Studio.usersettings. And in my case, an entire demo site instance — a fully configured Umbraco installation with a database, media files, and sample content that gets generated by a setup script and lives in a gitignoreddemo/directory. None of that is committed, but without it, a new worktree can't build, can't run, and can't be tested.The
.worktreeincludefile solved this. It's a simple file in the repo root that lists patterns for the non-committed files you want carried across into each new worktree:It's a small thing, but it's one of those details that makes the difference between "worktrees work in theory" and "worktrees work in practice."
Claude Code Gets Native Worktree Support
With the introduction of the
--worktreeflag, Claude Code now handles worktree creation natively. You just run:It creates a worktree, starts a session inside it, and even handles cleanup when you're done — removing the worktree automatically if there are no changes, or prompting you to keep it if there are. Session management, resume support, and subagent isolation all work out of the box.
It's genuinely well thought out. But it didn't quite line up with what I needed.
The built-in behaviour creates branches named
worktree-<name>and doesn't know anything about.worktreeinclude. Which meant adopting it as-is would break two things I cared about: my gitflow branch naming, and the local file copying that keeps worktrees functional from the moment they're created.WorktreeCreate Hooks: The Best of Both Worlds
What I hadn't initially noticed was that Claude Code also ships
WorktreeCreateandWorktreeRemovehooks. These fire when a worktree is being created or removed, and — this is the key part — they replace the default git behaviour entirely.The hook receives a JSON payload on stdin with the worktree name, and it's expected to print the absolute path to the created worktree on stdout. Everything else is up to you. Claude Code doesn't care how you create the worktree — it just needs a path back.
This meant I could use the official
claude -wsyntax while completely controlling what happens under the hood. No custom skill needed.The Implementation
The hook itself lives in
.claude/hooks/worktree-create.shand does three things: creates the worktree with a gitflow branch name (feature/<name>instead ofworktree-<name>), copies files matching.worktreeincludepatterns, and prints the worktree path to stdout for Claude Code to use.The key insight was the file copying. Rather than reimplementing gitignore pattern matching in bash — which is what my original skill did, complete with hand-rolled glob expansion and directory pruning — I realised I could just use git itself. The
.worktreeincludefile uses gitignore syntax, andgit ls-fileshas an--exclude-fromflag that accepts a gitignore-format file. Combined with--others --ignored, it returns exactly the untracked files matching those patterns. That single command replaced about 80 lines of find/prune/glob logic. Git handles all the pattern matching natively — globs,**for recursive matches,!for negation, trailing/for directories. All of it, correctly. The matched files then get bulk-copied via tar piping, which handles thousands of files efficiently.The corresponding
WorktreeRemovehook handles cleanup — runninggit worktree removewith a fallback for locked files on Windows.Here are the full files:
.claude/hooks/worktree-create.sh.claude/hooks/worktree-remove.sh.claude/settings.json(hooks section){ "hooks": { "WorktreeCreate": [ { "hooks": [ { "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/worktree-create.sh", "timeout": 60 } ] } ], "WorktreeRemove": [ { "hooks": [ { "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/worktree-remove.sh", "timeout": 30 } ] } ] } }What I Gained
The migration from a custom skill to hooks was a net simplification across the board:
Less code to maintain. The skill had a 760-line bash script handling creation, listing, switching, copying, removal, and cleanup. The hooks are about 80 lines each, and most of that is the worktree creation and path handling.
Official syntax, custom behaviour.
claude -w auth-refactorjust works — but it createsfeature/auth-refactorand copies my local config files. Anyone on the team can use the standard flag without knowing about the hooks.Better session management. Claude Code's built-in session handling — resume, cleanup prompts, subagent isolation — all comes for free. My custom skill had none of that.
Cross-platform path handling. One thing I had to get right was Windows compatibility. Claude Code sends Windows-style paths (
D:\Work\...) in the hook input, but Git Bash needs Unix-style paths (/d/Work/...). A pair ofcygpathwrapper functions at the top of the script handles the conversion at the boundaries — Unix internally, native format for the output path.The Pattern
What I like about this approach is the general pattern it demonstrates. Claude Code's hook system isn't just for blocking dangerous commands or running linters — it's an extension mechanism. When a built-in feature gets you 80% of the way there, hooks let you bridge the remaining 20% without maintaining a parallel implementation.
The
WorktreeCreatehook in particular is interesting because it fully replaces the default behaviour rather than augmenting it. That's a powerful escape hatch. It means Claude Code can ship opinionated defaults that work for most people, while teams with specific conventions can slot in their own logic and still benefit from all the surrounding infrastructure.I suspect more of my custom skills will follow this path as Claude Code's feature set continues to evolve — each one replaced not by a single feature, but by the right combination of a feature and a hook.
Until next time 👋
All reactions