Per-worktree config and shared hooks Jump to heading
All worktrees of a repository share one configuration file and one hooks directory. Usually that is exactly right: the remote, the signing setup and the team’s hooks apply everywhere. Sometimes it is not. A worktree used for a long release build might need a different core.sparseCheckout set; a worktree for a client project might need a different author email; a worktree for experiments might need hooks switched off. Git supports per-worktree configuration through an extension, and hooks can be shared or redirected through core.hooksPath. This page enables per-worktree config, shows which settings belong there, and sets up hooks that are shared by default with deliberate per-worktree exceptions, within Git worktrees for parallel development.
When to use this approach Jump to heading
- One worktree needs a setting that should not apply to the others.
- Sparse checkout should differ between worktrees.
- Hooks from a hook manager behave differently between worktrees.
- You script worktrees per task, as in scripting a worktree-per-ticket workflow.
Step 1 — See where configuration comes from Jump to heading
In a worktree, git config reads system, global and the repository’s shared config. Without the extension, git config writes to the shared file too, so a change made in one worktree applies to all.
git config --show-origin --list | grep -v '^file:/etc' | head -n 20
git rev-parse --git-common-dir # where the shared config lives Step 2 — Enable per-worktree configuration Jump to heading
Turn on the extension once per repository. Each worktree then has its own config.worktree file, and git config --worktree writes there.
git config extensions.worktreeConfig true
cd ../app-client-x
git config --worktree user.email "[email protected]"
git config --show-origin user.email
# file:/home/me/src/app/.git/worktrees/app-client-x/config.worktree [email protected] Enabling the extension marks the repository as needing a Git version that understands it. Versions from the last several years do.
Step 3 — Decide which settings belong per worktree Jump to heading
Most settings should stay shared. Move to per-worktree only those that genuinely differ by task.
# Good per-worktree candidates
git config --worktree core.sparseCheckout true
git config --worktree user.email "[email protected]"
git config --worktree core.hooksPath /dev/null # see Step 6, use with care
# Keep shared: remotes, signing configuration, merge and diff drivers, fetch refspecs When the extension is enabled, git sparse-checkout stores its settings per worktree automatically, so each worktree can check out a different cone.
Step 4 — Share hooks with core.hooksPath Jump to heading
Hooks live in the common Git directory, so all worktrees share them. Hook managers that install into .git/hooks therefore affect every worktree, which is usually what you want. A versioned hooks directory in the repository makes this explicit — but its contents come from each worktree’s own checkout.
git config core.hooksPath .githooks # relative: resolved in each worktree's own tree
ls .githooks
# pre-commit commit-msg pre-push A relative core.hooksPath resolves against each worktree’s root, so each worktree runs the hooks from its own branch. An absolute path runs the same hooks everywhere, regardless of branch.
Step 5 — Make hooks work in linked worktrees Jump to heading
Hook scripts that assume .git is a directory break in worktrees. Use Git to locate paths, and use the working tree root rather than the script’s location.
#!/bin/sh
# .githooks/pre-commit
top=$(git rev-parse --show-toplevel)
state=$(git rev-parse --git-path hook-state) # per-worktree path inside the git dir
mkdir -p "$state"
cd "$top" && ./scripts/lint-staged.sh Framework-based hooks, as in local hook configuration with Husky, set core.hooksPath themselves; check that the path they write works from every worktree.
Step 6 — Disable hooks in one worktree deliberately Jump to heading
For a scratch worktree used for experiments, you may want hooks off. Set it per worktree, so the main worktree keeps them, and remove the override when done.
git config --worktree core.hooksPath /dev/null
git config --worktree --unset core.hooksPath # restore ⚠️ SAFETY WARNING: Disabling hooks locally does not bypass server-side checks, and it should not become the default way to commit. Anything pushed from that worktree still has to pass CI and branch rules, so expect failures the hooks would have caught earlier.
Step 7 — Check the effective configuration per worktree Jump to heading
When behaviour differs between worktrees unexpectedly, compare effective configuration with origins in each.
for w in $(git worktree list --porcelain | awk '/^worktree / {print $2}'); do
echo "== $w"; git -C "$w" config --show-origin --get-regexp '^(user\.email|core\.hooksPath|core\.sparseCheckout)'
done Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Does git config --local write per worktree? Jump to heading
No. --local writes to the shared repository configuration. Use --worktree once the extension is enabled.
What happens if an old Git version opens the repository? Jump to heading
Versions that do not understand the extension refuse to operate on the repository rather than ignoring per-worktree settings. Upgrade Git on every machine that uses it.
Is the per-worktree file removed with the worktree? Jump to heading
Yes. git worktree remove deletes the worktree’s administrative directory, including config.worktree.
How do I skip hooks for a single commit instead? Jump to heading
Use git commit --no-verify for that one commit. It is narrower than a per-worktree override, and the team-wide guidance is in disabling Git hooks temporarily without breaking the team.
Related Jump to heading
- Git Worktrees for Parallel Development — the parent topic.
- Using Worktrees with IDEs and Language Servers — tools in worktrees.
- Sparse Checkout for Large Monorepos — different cones per worktree.
- Shipping a Team gitconfig with includeIf — directory-based configuration.