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

> Discover how the post-receive hook in no-mistakes resolves absolute gate paths by checking Git configuration and normalizing paths with the daemon's normalizeNotifyGatePath function for efficient Git operations.

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

---

**The post-receive hook in no-mistakes resolves absolute gate paths by first checking Git's `core.hooksPath` configuration or falling back to the script's own directory, then delegating final normalization to the daemon's `normalizeNotifyGatePath` function to handle relative inputs and symlinks.**

The **no-mistakes** repository relies on a `post-receive` hook to notify the daemon which gate repository triggered a push. Since Git may invoke hooks from ambiguous working directories—where `$(pwd)` resolves to `.` for bare repositories as documented in issue #269—the system must derive an absolute gate path before the daemon can reliably match the push to its pipeline.

## Why Bare Repositories Break Relative Path Resolution

When Git executes hooks in a bare repository, the current working directory often resolves ambiguously. In [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go), the developers documented that relying on `$(pwd)` alone produces unreliable results, particularly when the hook runs from paths like `.` that lack absolute context. This ambiguity prevents the daemon from correctly identifying which gate should trigger the pipeline, causing silent failures described in issue #269.

## Step 1: Hook-Side Resolution Using core.hooksPath

The hook generation logic in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) implements a two-tier fallback strategy inside the `PostReceiveHookScript` function. First, it attempts to read Git's `core.hooksPath` configuration relative to the repository's git directory. If that value is unset or unusable, it falls back to the directory containing the hook script itself using `$(dirname "$0")`.

This approach guarantees that the hook always derives an absolute directory pointing directly at the gate repository, regardless of where Git happens to invoke the script. The `InstallPostReceiveHook` function then writes this logic into the actual hook file.

```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 via normalizeNotifyGatePath

When the daemon receives the push notification via `daemon notify-push --gate <path>`, it runs `normalizeNotifyGatePath` defined in [`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go). This function performs two critical validation steps: it checks whether the supplied path is absolute using `filepath.IsAbs`, and if relative, resolves it against the daemon's current working directory via `filepath.Abs`.

Finally, it canonicalizes the result through `filepath.EvalSymlinks` to eliminate any symbolic links or redundant path elements.

```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)
}

```

## Step 3: Supporting Legacy Hook Installations

The system maintains backward compatibility with older hook versions that may pass relative `--gate` arguments such as `.`. The daemon's normalizer, as tested in [`internal/cli/daemon_cmd_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd_test.go) and [`internal/git/hook_test.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook_test.go), automatically converts these legacy values into absolute paths. This prevents the "pipeline silently never starts" bug originally reported in issue #269, ensuring that even repaired or partially upgraded gates function correctly without requiring immediate hook reinstallation.

## Summary

- The `PostReceiveHookScript` in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) derives an initial absolute path by checking `core.hooksPath` or falling back to `$(dirname "$0")`.
- The `normalizeNotifyGatePath` function in [`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go) converts any relative daemon arguments to absolute paths and resolves symlinks via `filepath.EvalSymlinks`.
- Legacy hooks passing relative `--gate` values remain supported through automatic normalization in the daemon.
- Together, these mechanisms ensure reliable pipeline triggering regardless of Git's invocation directory.

## Frequently Asked Questions

### What happens if core.hooksPath is not configured?

If `core.hooksPath` is unset or returns an empty value, the hook script falls back to using `$(dirname "$0")` to locate the gate directory, as implemented in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go). This fallback ensures the hook always produces a usable absolute path even in default Git configurations.

### Why does the daemon need to normalize the path if the hook already resolved it?

The daemon normalizes paths to handle edge cases where legacy hooks pass relative values like `.` and to eliminate symbolic links that might cause path comparison failures. This double-validation ensures the daemon's internal gate registry matches the actual filesystem location exactly.

### How does no-mistakes handle symbolic links in gate paths?

The `normalizeNotifyGatePath` function calls `filepath.EvalSymlinks` to resolve any symbolic links in the final absolute path. This canonicalization prevents scenarios where the same gate could be referenced through multiple path aliases, ensuring consistent pipeline association.

### Where is the fix for issue #269 implemented?

The resolution for issue #269 spans both [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) where the hook generates absolute paths, and [`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go) where the daemon validates them. The fix is documented in [`CHANGELOG.md`](https://github.com/kunchenguid/no-mistakes/blob/main/CHANGELOG.md) under versions addressing issues #269 and #358.