# 0030. A pre-push hook, and no pre-commit hook

- **Status:** Accepted
- **Date:** 2026-08-08

## Context

"Run the unit tests before every push, always, no exceptions" was a sentence in `docs/TESTING.md` and nothing
else. Nothing prevented a push with a red suite.

[ADR 0023](0023-unit-tests-without-external-dependencies.md) listed a `pre-push` hook among its rejected
alternatives, "as a mechanism the developer did not want in the way". The objection was to a gate that costs
time on every operation — and this suite has no database and finishes in under a tenth of a second, while
pushes happen once per task. The cost the objection was about does not exist here.

## Decision

**A `pre-push` hook runs `composer test` and refuses the push on a red suite. There is no pre-commit hook.**

- The hooks live in `bin/hooks/` so they are versioned, reviewable and diffable.
- `bin/install-hooks` symlinks them into `.git/hooks`. It has to be run **once per clone**.
- The hook runs `composer test` in the `raktar24-php-fpm` container and refuses to run at all if the
  container is down, rather than passing silently.
- `git push --no-verify` bypasses it.
- **Pre-commit deliberately stays empty.** The workflow commits once per review round and `--amend`s small
  corrections; a gate there would tax exactly the commits it encourages.

This reverses the specific alternative rejected in ADR 0023. The rest of 0023 stands.

## Alternatives

- **Keeping the rule written-only.** It works right up until the one time it does not, and the failure is a
  red master.
- **A `core.hooksPath` pointing at `bin/hooks`.** One config setting instead of symlinks, and it silently
  replaces any other hook configuration in the clone. The symlinks are per-hook and visible in
  `.git/hooks/`.
- **Running the gate in CI instead.** There is no CI here, and adding one for a sub-second suite is a larger
  decision than this.

## Consequences

- `bin/install-hooks` has to be run once per clone, and **a missed run is silent** — nothing tells you the
  hook is not there. This is the known weakness; the written rule in TESTING.md remains the real guarantee,
  and the hook is a backstop.
- The hook needs Docker running. Refusing the push when the container is down is deliberate: a skipped gate
  that reports success is worse than a blocked push.
- If the suite ever grows slow enough to be felt on a push, this ADR is the thing to revisit — not the rule.
