# Git hooks

> Block bad commits at pre-commit, commit-msg, or pre-push with a one-command CommitBrief hook scaffolder.

CommitBrief docs · v1.x · Operations

Canonical URL: https://commitbrief.com/docs/1.x/git-hooks

---

`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

```sh
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. The push is blocked on the first critical finding. |

`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.<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-ISO-timestamp>`. Write fresh. |
| 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>` is `0000…`) → skip.
- **New branch** (`<remote-sha>` is `0000…`) → review the tip
  commit.
- **Normal push** → review `<remote-sha>..<local-sha>`.

`--fail-on=critical` exits 1 on the first critical finding,
which blocks the push.

## Examples

```sh
# 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 (v1.8.0)

If your team already uses the [pre-commit](https://pre-commit.com)
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`

```yaml
repos:
  - repo: https://github.com/CommitBrief/commitbrief
    rev: v1.8.0                 # pin a released tag
    hooks:
      - id: commitbrief         # framework builds + pins the binary
```

Or, to use a `commitbrief` you've already installed:

```yaml
repos:
  - repo: https://github.com/CommitBrief/commitbrief
    rev: v1.8.0
    hooks:
      - id: commitbrief-system  # uses the commitbrief on PATH
```

Then install the framework's git hook once per clone:

```sh
pre-commit install
```

Like the git-native `pre-commit` body, both ids review the staged
change set and block the commit on a critical-severity finding.

## See also

- [Severity and CI gating](/docs/1.x/severity) — what
  `--fail-on=critical` actually means.
- [Review scopes](/docs/1.x/review-scopes#historic-ranges--commitbrief-diff) —
  the `diff` subcommand used by the pre-push body.
- [Safety and cost](/docs/1.x/safety-and-cost) — why the hook
  body uses `--no-cost-check` and `--quiet`.