Git Worktree and Git Hooks
Git hooks automate tasks on events like commits, pushes, and checkouts. When you use git worktree, understanding which hooks are shared and which can be overridden per-worktree is important for a consistent development experience.
Are Hooks Shared Across Worktrees?
Yes, by default. Git hooks live in the .git/hooks/ directory of the main repository. Linked worktrees do not have their own .git/hooks/directory — they share the hooks from the main worktree.
# Main worktree
myapp/
├── .git/
│ └── hooks/
│ ├── pre-commit # shared by all worktrees
│ ├── commit-msg
│ └── post-checkout
└── src/
# Linked worktree
myapp-feature/
├── .git # file, not directory
└── src/
# No hooks/ directory — uses main repo's hooksThis means a pre-commithook installed in your main worktree will also run when you commit in any linked worktree. For most teams, this is the desired behavior — you want the same linting and formatting checks everywhere.
core.hooksPath Configuration
Git 2.9 introduced the core.hooksPath configuration option, which lets you point hooks to any directory on disk. This setting is stored in the Git config and applies to all worktrees.
# Set a custom hooks directory (applies to all worktrees)
git config core.hooksPath .githooks
# Now Git looks for hooks in .githooks/ instead of .git/hooks/
# Since .githooks/ is a tracked directory, hooks are version-controlled
myapp/
├── .git/
├── .githooks/ # tracked directory
│ ├── pre-commit
│ └── commit-msg
└── src/This is a great pattern for teams because it puts hooks under version control. Every worktree and every developer gets the same hooks automatically. No manual setup required.
# Verify the current hooks path
git config --get core.hooksPath
# Reset to default (.git/hooks)
git config --unset core.hooksPathPer-Worktree Hook Overrides
Sometimes you want different hooks in different worktrees. For example, you might want to skip slow tests in a throwaway experiment worktree. You can achieve this with worktree-specific Git configuration.
# Set core.hooksPath for a single worktree only
cd ../myapp-experiment
# Use worktree-specific config (Git 2.20+)
git config --worktree core.hooksPath /dev/null
# This disables hooks only in this worktree
# Other worktrees still use the default hooksTo use worktree-specific configuration, you first need to enable the extensions.worktreeConfig extension:
# Enable worktree-specific config
git config extensions.worktreeConfig true
# Now you can use --worktree flag with git config
git config --worktree core.hooksPath ./my-local-hooks
# Worktree-specific config is stored in:
# .git/worktrees/<name>/config.worktreePre-commit Hooks with Worktrees
Pre-commit hooks are the most commonly used hooks, and they work seamlessly with worktrees. The hook runs in the worktree where you execute git commit, so it has access to the correct working directory and staged files.
#!/bin/sh
# .githooks/pre-commit
# This runs correctly in any worktree
# Lint staged files
echo "Running linter..."
npx lint-staged
# Run type checking
echo "Running type check..."
npx tsc --noEmit
# The hook runs in the worktree's directory,
# so it uses that worktree's node_modules and tsconfigOne thing to watch: if your pre-commit hook relies on node_modules, make sure you have run npm install in the worktree first. See the node_modules guide for details.
Husky and lint-staged Considerations
Husky is the most popular tool for managing Git hooks in JavaScript projects. It works with worktrees, but there are a few things to keep in mind.
Husky v9+ setup
Husky v9 uses core.hooksPath to point to a .husky/ directory in the project root. Since this is a tracked directory and core.hooksPath is a shared configuration, hooks work in all worktrees automatically.
# Husky v9 setup (works with worktrees out of the box)
npx husky init
# This creates .husky/ directory and sets core.hooksPath
# The .husky/ directory is tracked in Git
.husky/
├── pre-commit # runs in all worktrees
└── _/
└── husky.shImportant: install dependencies first
Husky hooks typically invoke npx lint-staged or similar tools. These commands require node_modules to be installed. If you commit in a worktree without running npm install first, the hook will fail.
# Common error when node_modules is missing in a worktree
$ git commit -m "add feature"
.husky/pre-commit: npx: not found
# or
sh: lint-staged: command not found
# Fix: install dependencies in the worktree
npm install
# Then commit againlint-staged configuration
lint-staged runs linters on staged files only. It works correctly in worktrees because it operates on the current working directory and the current Git index, both of which are per-worktree.
// package.json (or .lintstagedrc)
{
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.css": ["prettier --write"]
}
}
// This configuration is tracked in Git and works in every worktree