# How the no-mistakes Git Hook Intercepts git push to Route Through the Gate Remote

> Discover how the no-mistakes Git hook intercepts git push operations by installing a post-receive script in a gate repository The hook routes pushes through the gate remote to trigger pipelines without blocking your workflow

- Repository: [Kun Chen/no-mistakes](https://github.com/kunchenguid/no-mistakes)
- Tags: internals
- Published: 2026-07-04

---

**The no-mistakes Git hook intercepts pushes by installing a post-receive script in a bare "gate" repository that resolves push metadata and invokes `no-mistakes daemon notify-push` to trigger the pipeline without blocking the push operation.**

The `kunchenguid/no-mistakes` tool creates a dedicated **gate** — a bare Git repository that acts as the pipeline’s entry point. When you push to the remote named `no-mistakes`, Git fires a custom post-receive hook that captures the push details and forwards them to the daemon, ensuring every commit runs through your review and CI pipeline before reaching the main repository.

## The Gate Remote Architecture

`no-mistakes` establishes a **gate remote** that points your working copy to a local bare repository. This remote, always named `no-mistakes`, serves as the interception point.

The setup is orchestrated by functions in [`internal/gate/gate.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/gate/gate.go):

- **`ensureWorkingRemote`** (lines 19–21) adds the `no-mistakes` remote to your working repository if it does not exist, or repairs it during refresh operations.
- **`Init` and `InitWithFork`** (lines 28–38) create the bare gate repository under `$NM_HOME/repos/<repo-id>.git`, install the hook, and wire up the remote.

This architecture guarantees **idempotency** — running `init` multiple times only adds the remote when missing, never overwriting user-managed remotes.

## The Post-Receive Hook Mechanism

When you execute `git push no-mistakes <branch>`, Git updates the bare gate repository and triggers the **post-receive hook** installed at `<gate>/.git/hooks/post-receive`. The hook is generated by `PostReceiveHookScript` in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) (lines 15–89).

The hook operates **non-blocking**; Git ignores its exit status, meaning your push succeeds even if the pipeline daemon fails to start.

### Resolving the Gate Directory

The hook must reliably locate the gate directory even when the current working directory collapses to `.` (issue #269). It uses:

```bash
GATE_DIR=$(git rev-parse --absolute-git-dir 2>/dev/null || pwd)

```

This guarantees an absolute path to the bare repository, ensuring the daemon always receives a valid `--gate` argument regardless of how Git invokes the hook.

### Collecting Push Metadata

The hook parses the push data from Git’s standard input and environment variables. It collects:

- **Old SHA** (`$oldrev`) and **new SHA** (`$newrev`)
- **Reference name** (`$refname`)
- **Push options** from `GIT_PUSH_OPTION_COUNT` and `GIT_PUSH_OPTION_n` variables

The script builds the argument list incrementally:

```bash
set -- --gate "$GATE_DIR" \
       --ref "$refname" \
       --old "$oldrev" \
       --new "$newrev"

# Append any push-options

while [ "$i" -lt "${GIT_PUSH_OPTION_COUNT:-0}" ]; do
    opt=$(printenv "GIT_PUSH_OPTION_$i" 2>/dev/null || :)
    set -- "$@" --push-option "$opt"
    i=$((i + 1))
done

```

### Invoking the Daemon

With arguments assembled, the hook invokes the daemon:

```bash
out=$(NM_HOOK_HELPER=1 "$NM_BIN" daemon notify-push "$@" 2>&1)
status=$?

```

The **`NM_HOOK_HELPER=1`** environment variable signals to the binary that it is running from a hook context, disabling interactive UI elements. The `daemon notify-push` command (registered in [`internal/daemon/daemon.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/daemon.go)) receives the gate path, ref names, and options to create a new pipeline run.

If the daemon exits non-zero, the hook logs the failure to `$GATE_DIR/notify-push.log` (lines 64–66 in [`hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/hook.go)) and echoes the error to stderr so the pushing client sees it. On success, it prints a concise ASCII banner indicating the pipeline has started.

## Step-by-Step Push Interception Flow

1. **Initialization**: `no-mistakes init` creates the bare gate, installs the post-receive hook via `git.InstallPostReceiveHook`, and adds the `no-mistakes` remote via `ensureWorkingRemote`.

2. **Push Execution**: You run `git push no-mistakes HEAD:refs/heads/feature`. Git contacts the bare gate and updates the ref.

3. **Hook Fires**: The post-receive script resolves `$GATE_DIR` using `git rev-parse --absolute-git-dir`.

4. **Metadata Extraction**: The hook reads the old SHA, new SHA, and ref name from Git’s hook input, then iterates through `GIT_PUSH_OPTION_*` variables to capture push options.

5. **Daemon Notification**: The hook executes `no-mistakes daemon notify-push` with the `--gate`, `--ref`, `--old`, `--new`, and `--push-option` flags.

6. **Error Handling**: Failures are written to `notify-push.log` inside the gate directory and printed to stderr. The hook always exits cleanly to avoid blocking the Git operation.

7. **Pipeline Start**: The daemon creates a new run in the database, beginning the review → test → lint pipeline using the gate’s bare repository as the source of truth.

## Why the Gate Remote Is Essential

The gate remote provides three critical guarantees:

- **Isolation**: The gate is a bare repository; its history never contains unreviewed developer commits, ensuring the pipeline always validates clean states.
- **Deterministic Source**: All pipeline steps read from the gate’s remote-tracking refs, guaranteeing they process the exact commit the daemon received.
- **Non-Destructive Setup**: The `ensureWorkingRemote` logic only modifies the `no-mistakes` remote, leaving your `origin` and other remotes untouched.

## Usage Example

Initialize the gate and push a feature branch:

```bash

# Initialize the gate (creates bare repo, installs hook, adds remote)

$ no-mistakes init

# Create and commit a feature

$ git checkout -b feat/optimize
$ echo "optimized code" > main.go
$ git add main.go && git commit -m "optimize performance"

# Push to the gate remote

$ git push no-mistakes HEAD:refs/heads/feat/optimize

```

The hook outputs a banner confirming the intercept:

```text
_  _ ____    _  _ _ ____ ___ ____ _  _ ____ ____
...
* Pipeline started
Run no-mistakes to review.

```

If the daemon fails to start, check the persistent log:

```bash
$ cat ~/.no-mistakes/repos/<repo-id>.git/notify-push.log

```

## Summary

- **The gate remote** (`no-mistakes`) routes pushes through a bare repository that acts as the pipeline entry point.
- **The post-receive hook** in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) intercepts pushes, resolves the absolute gate path, and forwards metadata to the daemon.
- **Non-blocking execution** ensures Git pushes succeed regardless of pipeline daemon status.
- **Push metadata** including SHAs, ref names, and push options are passed via `daemon notify-push` arguments.
- **Error logs** persist in `notify-push.log` inside the gate directory for debugging daemon connectivity issues.

## Frequently Asked Questions

### Does the hook block my push if the pipeline fails?

No. The hook operates non-blocking, meaning Git ignores the exit status of the post-receive script. Your push succeeds immediately, and any pipeline failures are logged to `notify-push.log` inside the gate directory while the error message is echoed to your terminal.

### Where does the hook log errors when the daemon is unreachable?

The hook writes failures to `$GATE_DIR/notify-push.log` (as implemented in [`hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/hook.go) lines 64–66), where `$GATE_DIR` is the absolute path to the bare gate repository. You can view this log to diagnose why the `no-mistakes daemon notify-push` command failed.

### What is the `NM_HOOK_HELPER=1` environment variable for?

This variable tells the `no-mistakes` binary that it is being invoked from a Git hook rather than an interactive terminal. When set, the CLI disables interactive UI elements and streams output appropriately for the hook context, as seen in the hook script generated by `PostReceiveHookScript`.

### How does the hook handle Git push options?

The hook iterates through the `GIT_PUSH_OPTION_COUNT` and `GIT_PUSH_OPTION_n` environment variables (where `n` is an index), appending each option to the daemon command via `--push-option` flags. This allows the pipeline to receive additional metadata, such as CI configuration flags, passed during `git push --push-option`.