# How the post-receive Hook Resolves Absolute Gate Paths in no-mistakes

> Learn how the post-receive hook at kunchenguid/no-mistakes resolves absolute gate paths using git core.hooksPath and normalizeNotifyGatePath to prevent pipeline failures.

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

---

**The post-receive hook derives an absolute gate path by querying Git's `core.hooksPath` configuration—or falling back to the script's own directory—and then relies on the daemon's `normalizeNotifyGatePath` function to canonicalize the path and eliminate relative references like `.` that would otherwise cause pipelines to silently fail.**

The no-mistakes CI/CD system triggers pipelines when developers push to **gate** repositories (bare repositories that drive the pipeline). When Git invokes the `post-receive` hook, the current working directory is often ambiguous; in bare repositories, `$(pwd)` resolves to `.` (see issue #269), making relative paths unreliable. To prevent the daemon from receiving an unusable path, the hook implements a two-step resolution strategy that guarantees an absolute, canonical gate directory.

## Why Relative Paths Fail in Bare Repositories

Git hooks execute in the context of the repository, but the working directory is not guaranteed to be the repository root. In bare repositories, the working directory is typically the repository directory itself, yet shell commands may resolve `.` differently depending on the Git version or invocation method. If the hook passed a relative path like `.` to the daemon, the daemon would attempt to locate the gate relative to its own process directory, causing the pipeline to never start. This vulnerability requires the hook to resolve an absolute path before notifying the daemon.

## Step 1: Hook-Side Resolution in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go)

The `PostReceiveHookScript` function (called by `InstallPostReceiveHook`) generates a shell script that performs the first line of defense. The script uses a priority-based approach to locate the gate directory, then converts it to an absolute path before invoking the daemon.

### Checking `core.hooksPath` First

The script attempts to read Git's `core.hooksPath` configuration, interpreting it relative to the repository's git directory. This supports custom hook installation paths.

### Fallback to the Script Directory

If `core.hooksPath` is unset or empty, the script falls back to `$(dirname "$0")`—the directory containing the hook file itself. It then uses `cd "$gate_dir" && pwd` to resolve any symbolic links and return a clean absolute path.

```sh
#!/bin/sh

# no-mistakes post-receive hook

# Resolve gate directory:

#   1. Prefer git's core.hooksPath if configured

#   2. Fallback to the directory of this script

#   3. Convert to an absolute path

gate_dir="$(git -C "$(dirname "$0")/.." config --get core.hooksPath 2>/dev/null)"
if [ -z "$gate_dir" ]; then
    gate_dir="$(dirname "$0")"
fi
gate_dir="$(cd "$gate_dir" && pwd)"

# Notify the daemon (non‑blocking)

"$NM_BIN" daemon notify-push --gate "$gate_dir" &

```

## Step 2: Daemon-Side Normalization in [`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go)

Even with the hook's precautions, the daemon must sanitize the incoming `--gate` argument. The `normalizeNotifyGatePath` function in [`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go) serves as a final safeguard, handling legacy hooks that may still pass relative paths and cleaning up any filesystem irregularities.

### Handling Relative Paths and Symlinks

The function checks `filepath.IsAbs(gate)`. If the path is relative, it resolves it against the daemon's current working directory using `filepath.Abs`. Finally, it calls `filepath.EvalSymlinks` to collapse any symbolic links into their canonical targets.

```go
func normalizeNotifyGatePath(gate string) (string, error) {
    // If the user supplied a relative path (e.g. "."), resolve it.
    if !filepath.IsAbs(gate) {
        abs, err := filepath.Abs(gate)
        if err != nil {
            return "", err
        }
        gate = abs
    }
    // Clean up any symlinks / redundant elements.
    return filepath.EvalSymlinks(gate)
}

```

### Legacy Hook Compatibility

Older versions of no-mistakes allowed hooks to pass relative paths such as `.` directly to the daemon. The normalization logic in [`internal/cli/daemon_cmd_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd_test.go) verifies that these legacy values are correctly converted to absolute paths, preventing the "pipeline silently never starts" regression described in issue #269.

## Summary

- **The hook script** ([`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go)) prefers `core.hooksPath` but falls back to its own directory, using `cd && pwd` to guarantee an absolute path before calling the daemon.
- **The daemon** ([`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go)) runs `normalizeNotifyGatePath` to convert any remaining relative paths (including legacy `.` arguments) to absolute paths and resolves symlinks via `filepath.EvalSymlinks`.
- **Together**, these mechanisms ensure the daemon receives a canonical absolute path to the gate repository, eliminating ambiguity from bare repository working directories and preventing silent pipeline failures.

## Frequently Asked Questions

### What is a gate in no-mistakes?

A **gate** is a bare Git repository configured with a `post-receive` hook that triggers the no-mistakes pipeline daemon. When you push to the gate, the hook notifies the daemon to start processing the new commit.

### Why can't the hook simply use `$(pwd)` to find the gate?

In bare repositories, the shell's current working directory may resolve to `.` rather than the actual filesystem path, particularly when Git invokes the hook from an unexpected directory (issue #269). Relying on `$(pwd)` risks passing a relative reference that the daemon cannot resolve correctly.

### How does the daemon handle symlinks in the gate path?

The `normalizeNotifyGatePath` function calls `filepath.EvalSymlinks` after converting the path to absolute form. This collapses any symbolic links into their canonical targets, ensuring the daemon compares the actual inode paths when matching the gate to registered repositories.

### What happens if `core.hooksPath` is set to a relative value?

The hook script resolves `core.hooksPath` relative to the repository's git directory before converting it to an absolute path with `cd && pwd`. If the value is invalid or empty, the script falls back to the directory containing the hook file itself.