Git hooks
Block bad commits at pre-commit, commit-msg, or pre-push with a one-command CommitBrief hook scaffolder.
commitbrief install-hook drops a small shell script at
.git/hooks/<name> that runs CommitBrief against the relevant
change set, blocking the git operation on a critical-severity
finding.
Quick install
commitbrief install-hook # default: pre-commit
commitbrief install-hook --hook=pre-push
commitbrief install-hook --uninstall # remove (only if we wrote it)
Supported hooks
| Hook | Default? | What the body does |
|---|---|---|
pre-commit | ✓ | Runs commitbrief --staged --fail-on=critical --quiet --no-cost-check. Blocks the commit on any critical-severity finding. |
commit-msg | — | Same body as pre-commit. Useful when you want the review to happen after the commit message is composed. |
pre-push | — | Reads git’s per-ref stdin protocol and runs commitbrief diff <remote>..<local> --fail-on=critical --quiet --no-cost-check for each ref being pushed. Skips branch deletions; for new branches reviews the tip commit (<sha>^..<sha>). The push is blocked on the first critical finding. Requires v1.18.1 or later for the new-branch behavior; see Pre-push specifics. |
post-commit, post-receive, and other hooks are intentionally
not supported.
Flags
| Flag | Notes |
|---|---|
--hook=<name> | Which hook to install. One of pre-commit, commit-msg, pre-push. Default pre-commit. |
--uninstall | Remove a hook previously written by install-hook. Refuses to touch a hook that does not carry our generated-marker comment. |
--yes (global) | Overwrite an existing hook file. The previous content is backed up to <name>.bak.<UTC timestamp>. |
Conflict handling
| Situation | Behavior |
|---|---|
| Target hook does not exist | Write it. Print Installed <path>. |
Hook exists, our marker, no --yes | Refuse with install-hook: <path> already exists; re-run with --yes to back it up and overwrite. |
Hook exists, our marker, with --yes | Rename existing to <path>.bak.<UTC timestamp> (compact form, for example 20260101T150405Z). Write fresh. |
Hook exists, NO marker, no --yes | Refuse with the same already exists; re-run with --yes message. |
Hook exists, NO marker, with --yes | Same backup + overwrite — explicit opt-in. |
--uninstall, file missing | No-op success. |
--uninstall, marker present | Remove. Print Removed <path>. |
--uninstall, marker missing | Refuse — was not written by commitbrief; refusing to remove. |
The generated marker
Every hook this command writes contains the verbatim comment:
Generated by `commitbrief install-hook`
--uninstall greps for it before removing. A hand-written hook
of the same name is never silently clobbered.
Embedded absolute path
The generated hook embeds the absolute path of the running
commitbrief binary as a single-quoted shell token instead of
relying on $PATH lookup. Resolved via os.Executable() plus
filepath.EvalSymlinks so the path survives brew upgrade (which
swaps the keg symlink target).
This makes the hook work under GUI git clients (Tower, GitHub
Desktop, Fork, JetBrains IDEs) that strip the user’s shell
$PATH and would otherwise fail to find commitbrief if it sits
under /opt/homebrew/bin/.
Pre-push specifics
The pre-push body parses git’s per-ref stdin protocol:
<local-ref> <local-sha> <remote-ref> <remote-sha>
per line. For each ref:
- Branch deletion (
<local-sha>is0000…) → skip. - New branch (
<remote-sha>is0000…) → review the tip commit only, as<sha>^..<sha>. A root commit is reviewed against the empty tree. If the parent is not available locally (for example a shallow clone), the ref is skipped with a warning; fetch more history to review that push. - Annotated tag → peeled to the commit it points at first; a tag that does not resolve to a commit is skipped.
- Normal push → review
<remote-sha>..<local-sha>.
Every range has two ends, so uncommitted changes in your working tree are never part of the reviewed diff.
Requires v1.18.1 or later. Earlier versions passed the bare
commit to commitbrief diff for a new branch, which compared that
commit with the working tree instead of reviewing the pushed
commit. The hook is a generated file, so re-run
commitbrief install-hook --hook=pre-push --yes after upgrading to
regenerate it; an already installed hook keeps the old body.
--fail-on=critical exits 1 on the first critical finding,
which blocks the push.
Examples
# Default: install pre-commit hook.
commitbrief install-hook
# Pre-push hook for catch-on-push semantics.
commitbrief install-hook --hook=pre-push
# Overwrite an existing hook (backup auto-created).
commitbrief install-hook --yes
# Remove the hook we installed earlier.
commitbrief install-hook --uninstall
# Pre-push variant.
commitbrief install-hook --hook=pre-push --uninstall
pre-commit framework integration
If your team already uses the pre-commit
framework to manage hooks, CommitBrief ships a .pre-commit-hooks.yaml
so you can add it to any repo’s .pre-commit-config.yaml in one entry —
no install-hook run, no hand-written shell script.
This is distinct from install-hook above. install-hook writes a
git-native script straight into .git/hooks/ and embeds the absolute
binary path; the pre-commit framework owns .git/hooks/ itself and
dispatches to its configured repos. Pick one approach per repo — don’t
run both against the same hook.
Two hook ids
| Hook id | language | How the binary is provided |
|---|---|---|
commitbrief | golang | The framework builds and pins CommitBrief from source for you — nothing to install separately. |
commitbrief-system | system | Uses an already-installed commitbrief on PATH (e.g. from Homebrew, Scoop, or go install). |
Reach for commitbrief when you want the framework to manage the
toolchain end-to-end, and commitbrief-system when CommitBrief is
already installed and you’d rather not rebuild it.
Example .pre-commit-config.yaml
repos:
- repo: https://github.com/CommitBrief/commitbrief
rev: v1.18.1 # pin a released tag
hooks:
- id: commitbrief # framework builds + pins the binary
Or, to use a commitbrief you’ve already installed:
repos:
- repo: https://github.com/CommitBrief/commitbrief
rev: v1.18.1
hooks:
- id: commitbrief-system # uses the commitbrief on PATH
Then install the framework’s git hook once per clone:
pre-commit install
Both ids review the staged change set and run with
args: [--staged, --fail-on=high], so they block the commit on a
high-severity or worse finding. This differs from the git-native
pre-commit body, which gates on critical. To change the gate,
override args in your config:
hooks:
- id: commitbrief
args: [--staged, --fail-on=critical]
See also
- Review before you commit — a guided walkthrough of running a review by hand or via this hook.
- Severity and CI gating — what
--fail-on=criticalactually means. - Review scopes —
the
diffsubcommand used by the pre-push body. - Safety and cost — why the hook
body uses
--no-cost-checkand--quiet.