# What Is the Post-Receive Hook in no-mistakes? A Deep Dive into Git Integration

> Discover the post-receive hook in no-mistakes. This Git integration feature automatically triggers pipeline runs and handles ref updates, ensuring seamless development workflows.

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

---

**The post-receive hook in no-mistakes acts as a non-blocking bridge between Git pushes and the no-mistakes daemon, automatically triggering pipeline runs by resolving the bare repository path and notifying the daemon of every ref update.**

The post-receive hook in no-mistakes serves as the critical integration point between Git operations and the continuous integration system. Located in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go), this component generates a shell script that intercepts every push to execute the `daemon notify-push` command, ensuring that code changes immediately trigger appropriate pipeline runs without blocking the developer's workflow.

## How the Hook Script Is Generated

The hook begins its lifecycle in the `PostReceiveHookScript` function found in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) (lines 15-20). This function constructs a POSIX-compliant shell script that handles the communication between Git and the no-mistakes daemon.

The generated script performs several critical tasks:

- Resolves the absolute path to the no-mistakes binary
- Determines the bare repository directory (`GATE_DIR`) using `git rev-parse --absolute-git-dir`
- Iterates through every ref being pushed
- Invokes `no-mistakes daemon notify-push` with appropriate flags
- Handles errors gracefully without failing the push

## Resolving the Repository Path Reliably

One of the most sophisticated aspects of the implementation is the **absolute gate resolution** logic in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) (lines 39-58). This handles edge cases where Git executes hooks from a "." working directory, which would cause `--gate .` to resolve incorrectly.

The script first attempts to determine the repository location via `git rev-parse --absolute-git-dir`. If this command fails, it falls back to deriving the path from the hook file's own location. This dual strategy ensures the `daemon notify-push` command always receives a valid, absolute path to the bare repository, preventing pipeline runs from targeting the wrong directory.

## The Notification Mechanism

When a developer executes `git push`, the post-receive hook reads each ref update from stdin and enters the **notify loop** located in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) (lines 62-78).

For every reference updated, the script constructs and executes the following command:

```bash
no-mistakes daemon notify-push \
  --gate "$GATE_DIR" \
  --ref "$refname" \
  --old "$oldrev" \
  --new "$newrev"

```

The daemon, implemented in [`internal/daemon/manager.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/daemon/manager.go), receives this notification and immediately queues a new pipeline run. The hook passes any additional push options through to the daemon, allowing for parameterized pipeline execution based on the specific push characteristics.

## Non-Blocking Semantics and Error Handling

A key design principle of the no-mistakes post-receive hook is that it **never blocks the Git client**. According to the implementation in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) (lines 79-86), the hook's exit status is intentionally ignored by Git, meaning:

- Push operations succeed regardless of hook failures
- Network issues or daemon downtime cannot prevent code pushes
- Developers receive immediate feedback that their code was accepted

When errors do occur, they are written to **stderr** and appended to `notify-push.log` inside the bare repository. This log file serves as the primary diagnostic tool for administrators debugging why a pipeline failed to trigger.

Upon successful notification, the script prints a success banner (lines 90-99) encouraging users to run `no-mistakes` to review the pipeline status, providing a seamless transition from push to monitoring.

## Installing and Managing the Hook

The no-mistakes CLI provides first-class support for hook lifecycle management through two primary functions in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go):

**`InstallPostReceiveHook`** writes the generated script into `<bare-repo>/hooks/post-receive`, ensuring the correct shebang and permissions are set for immediate execution.

**`RefreshManagedPostReceiveHook`** updates existing hooks only if they were originally installed by no-mistakes. This safety check prevents overwriting custom user hooks or those managed by other tools like Husky.

### Isolating from External Configuration Changes

To prevent other Git tools from accidentally disabling the hook, the `IsolateHooksPath` function (lines 74-84) pins the `core.hookspath` configuration in the per-work-tree Git config. This protective measure ensures that global Git configuration changes or other hook management frameworks cannot redirect the hook execution away from the no-mistakes script.

## Practical Implementation Examples

### Installing the hook for a new bare repository

```go
import "github.com/kunchenguid/no-mistakes/internal/git"

func setupBareRepo(path string) error {
    // Create the bare repository first...
    return git.InstallPostReceiveHook(path)
}

```

### Refreshing an existing installation

```go
changed, err := git.RefreshManagedPostReceiveHook(bareRepoPath)
if err != nil {
    log.Fatal(err)
}
if changed {
    fmt.Println("no-mistakes hook updated to latest version")
}

```

### Examining the generated script

The actual shell script generated by `PostReceiveHookScript` follows this pattern:

```sh
#!/bin/sh

# no-mistakes post-receive hook

# Notifies the daemon of the push. Non-blocking: post-receive exit code is

# ignored by git, so we never reject the push here.

NM_BIN='no-mistakes'   # Resolved automatically at generation time

# Resolve the bare repo directory reliably

GATE_DIR=$(git rev-parse --absolute-git-dir 2>/dev/null) || \
  GATE_DIR=$(cd "$(dirname "$0")/../.." && pwd)

# Read stdin for ref updates and notify daemon for each

while read oldrev newrev refname; do
    NM_HOOK_HELPER=1 "$NM_BIN" daemon notify-push \
        --gate "$GATE_DIR" \
        --ref "$refname" \
        --old "$oldrev" \
        --new "$newrev"
done

# Error handling: log to notify-push.log but don't exit non-zero

# ... success banner printed to user ...

```

## Summary

- The post-receive hook in no-mistakes is generated dynamically by `PostReceiveHookScript` in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) to ensure the correct binary paths and repository locations.
- **Absolute gate resolution** prevents path resolution errors by using `git rev-parse` with a fallback to the hook's own location.
- The hook invokes `no-mistakes daemon notify-push` for every ref update, passing the gate path and revision hashes to trigger pipeline runs.
- Execution is **non-blocking** by design—the hook always exits cleanly to avoid interfering with Git operations, with errors logged to `notify-push.log`.
- **Installation helpers** like `InstallPostReceiveHook` and `RefreshManagedPostReceiveHook` provide safe lifecycle management without overwriting custom user hooks.
- The `IsolateHooksPath` function protects the hook from being disabled by external Git configuration changes.

## Frequently Asked Questions

### What happens if the post-receive hook fails to notify the daemon?

The push succeeds regardless of hook failures. According to the implementation in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) (lines 79-86), errors are written to stderr and appended to `notify-push.log` inside the bare repository, but the hook always exits with status 0 to avoid blocking the Git client.

### How does no-mistakes handle existing custom hooks?

The `RefreshManagedPostReceiveHook` function in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) checks whether the existing hook is a no-mistakes-managed hook before updating it. This ensures that custom user hooks or those managed by other tools remain untouched, preventing accidental overwrites while allowing seamless updates for no-mistakes installations.

### Where can I find logs from the post-receive hook execution?

Hook execution logs are stored in `notify-push.log` inside the bare repository's root directory. This file captures stderr output from both the hook script itself and the `daemon notify-push` command, making it the primary location for debugging why a pipeline failed to trigger after a push.

### Can the post-receive hook reject or block Git pushes?

No, the hook is explicitly designed to be non-blocking. As noted in the generated script comments and implemented in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) (lines 79-86), Git ignores the exit status of post-receive hooks. The no-mistakes hook leverages this behavior to ensure that daemon availability or pipeline configuration issues never prevent developers from pushing code.