Git hooks let you run scripts automatically on commit, push, or
merge. But they have always had a few rough edges: the scripts live
in .git/hooks/, which is never committed to
the repository, each event can only have one script, and there is no
built-in way to see what hooks are active or where they come from.
Git 2.54 addresses all three with config-based hooks. You can now
define hooks directly in your Git configuration files.
#
Config-based hooks in Git 2.54
Instead of placing an executable script at
.git/hooks/pre-commit, you can now write this
in your .gitconfig or
.git/config:
[hook "lint"]
event = pre-commit
command = ./hooks/pre-commit-lint
The hook.<name>.event key specifies which Git
event triggers the hook, and hook.<name>.command
is the command to run. The name (lint here) is
just a label, it can be anything you like.
Because this is standard Git config, it can live in any of the usual
config scopes:
-
-
~/.gitconfig, applies to all your repos
globally
-
-
.git/config, applies to a single
repository only
-
-
/etc/gitconfig, applies system-wide
#
Multiple hooks per event
Previously, each hook event could only have one script. If you
wanted to run both a linter and a secret scanner before a commit,
you needed a wrapper script that called both. With config-based
hooks, you just define them separately:
[hook "lint"]
event = pre-commit
command = ./hooks/pre-commit-lint
[hook "no-leaks"]
event = pre-commit
command = ./hooks/pre-commit-secrets
Git runs them in the order it encounters them in the config. The
traditional script at .git/hooks/pre-commit
still works too and runs last, so existing setups are unaffected.
See what's active
Use git hook list to inspect all configured
hooks for an event and see which config scope each one comes from:
$ git hook list pre-commit
global lint ./hooks/pre-commit-lint
local no-leaks ./hooks/pre-commit-secrets
#
Setting it up for a team
The actual hook scripts should be committed to the repository in a hooks/ directory. Make sure each script has the executable bit set:
mkdir hooks
chmod +x hooks/pre-commit-lint
Then each team member needs to register the hooks in their local
.git/config after cloning. This is two commands
per hook:
git config --local hook.lint.event pre-commit
git config --local hook.lint.command ./hooks/pre-commit-lint
Put these commands in your project's setup script — a
Makefile, justfile, or
a simple setup.sh — so new team members only need
to run it once after cloning.
# Makefile
setup:
git config --local hook.lint.event pre-commit
git config --local hook.lint.command ./hooks/pre-commit-lint
This is not automatic
Unlike Husky, which wires itself up via npm's prepare script on every
npm install, Git does not run any setup
automatically on clone. The one-time setup command is a conscious
trade-off: it works for any language stack (Python, Go, Rust)
without requiring Node.js or any additional tooling.
#
Disabling a hook per repository
If a hook is defined globally in ~/.gitconfig
but you want to opt a specific repository out, you can disable it locally
without removing the global config:
git config --local hook.lint.enabled false
This is particularly useful for personal global hooks like a secrets
scanner or a commit message linter that you want everywhere except
in a specific repo.
Config-based hooks are one of those small quality-of-life
improvements that quietly make Git more self-contained. The hooks
themselves are now first-class config: composable, inspectable with
git hook list, and easy to disable per repo
without touching anything globally.