# How the no-mistakes Post-Receive Hook Resolves Absolute Paths for the Gate Directory

> Learn how the no-mistakes post-receive hook resolves absolute paths for the gate directory using git rev-parse, hook location, and daemon re-verification to prevent failures. Optimize your Git workflows today.

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

---

**The no-mistakes post-receive hook resolves absolute paths for the gate directory by first querying `git rev-parse --show-toplevel`, falling back to the hook's own location via `dirname "$0"`, canonicalizing with `cd … && pwd`, and having the daemon re-verify the path through `normalizeNotifyGatePath` in [`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go) to prevent failures from relative path invocations.**

When the `kunchenguid/no-mistakes` daemon receives a push notification, it must know the exact absolute filesystem location of the **gate** (the bare repository it monitors). Because Git can invoke the post-receive hook from a working directory that collapses to `.` (see issue #269), the hook cannot rely on relative paths. Instead, it implements a two-stage resolution strategy that guarantees a canonical absolute path reaches the daemon.

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

The post-receive hook script is generated programmatically by the `PostReceiveHookScript` function in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go). This shell logic performs the first layer of path resolution before invoking the daemon.

### Git-First Resolution Strategy

The hook prioritizes Git’s own understanding of the repository root. It executes `git rev-parse --show-toplevel` to obtain the absolute path to the top-level working directory. This works correctly even when the hook runs from a subdirectory within the repository.

### Fallback to Hook Location

If Git cannot provide a path—such as when running in a bare repository without a working tree—the script falls back to the directory containing the hook file itself using `$(dirname "$0")`. This ensures a valid starting point regardless of the execution context.

### Shell Canonicalization

Once a candidate directory is identified, the script canonicalizes it to an absolute path using standard shell utilities:

```sh
gate_dir=$(git rev-parse --show-toplevel 2>/dev/null || dirname "$0")
gate_dir=$(cd "$gate_dir" && pwd)
exec "$NM_BIN" daemon notify-push --gate "$gate_dir" ...

```

The `cd "$gate_dir" && pwd` sequence resolves all symbolic links and relative components (like `./` or `../`), ensuring the `--gate` flag receives a clean, absolute path.

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

The daemon does not blindly trust the `--gate` argument provided by the hook. As a safeguard against legacy hooks or unexpected environments, it re-processes the incoming path through `normalizeNotifyGatePath`.

### The `normalizeNotifyGatePath` Safeguard

Located in [`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go), this function validates and re-absolutizes the gate path:

```go
func normalizeNotifyGatePath(gate string) (string, error) {
    if gate == "" {
        return "", errors.New("gate path missing")
    }
    // Resolve to an absolute, clean path
    abs, err := filepath.Abs(gate)
    if err != nil {
        return "", err
    }
    return abs, nil
}

```

The `filepath.Abs` call strips any remaining `./` prefixes and resolves the path against the current working directory if necessary. This defensive layer guarantees that the daemon stores a consistent, absolute reference to the gate directory, preventing the silent failures documented in [`AGENTS.md`](https://github.com/kunchenguid/no-mistakes/blob/main/AGENTS.md) regarding issue #269.

## Why Two Layers of Resolution Matter

The dual-resolution strategy provides defense in depth:

- **Hook-side logic** handles the common case where Git can locate the repository, while the shell fallback ensures portability across bare and non-bare repositories.
- **Daemon-side normalization** protects against edge cases where an older hook version or an unusual Git environment might pass a relative path.

Together, these mechanisms ensure that `no-mistakes` reliably resolves absolute paths for the gate directory regardless of how the post-receive hook was invoked.

## Summary

- The hook in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go) prefers `git rev-parse --show-toplevel` to find the repository root, falling back to `dirname "$0"` for bare repositories.
- Shell canonicalization via `cd … && pwd` converts the candidate path to an absolute format before passing it via `--gate`.
- The daemon’s `normalizeNotifyGatePath` function in [`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go) re-verifies the path using `filepath.Abs` to protect against relative path regressions.
- This two-stage approach prevents the working-directory collapse issue described in issue #269, ensuring the pipeline trigger always targets the correct repository.

## Frequently Asked Questions

### What happens if Git cannot resolve the repository root in a post-receive hook?

If `git rev-parse --show-toplevel` fails (common in bare repositories without a working tree), the hook script falls back to `$(dirname "$0")`, which extracts the directory containing the hook file itself. This fallback ensures the script can still determine a valid starting directory for path canonicalization.

### Why does the daemon re-validate the gate path with `normalizeNotifyGatePath`?

The daemon re-validates the path to defend against legacy hook versions or unusual Git environments that might send a relative path. By calling `filepath.Abs` in [`internal/cli/daemon_cmd.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/cli/daemon_cmd.go), the daemon guarantees it stores a canonical absolute path, preventing silent failures when the daemon’s working directory differs from the hook’s invocation context.

### Where is the post-receive hook script defined in the no-mistakes source code?

The hook script is generated by the `PostReceiveHookScript` function in [`internal/git/hook.go`](https://github.com/kunchenguid/no-mistakes/blob/main/internal/git/hook.go). This Go function returns a shell script template that embeds the resolution logic for the `gate_dir` variable before executing the daemon’s `notify-push` command.

### How does absolute path resolution prevent issue #269?

Issue #269 describes a failure mode where Git invokes the post-receive hook with a working directory of `.`, causing relative paths to resolve incorrectly against the daemon’s working directory rather than the repository. By converting the gate directory to an absolute path in the hook (via `cd … && pwd`) and re-verifying it in the daemon, no-mistakes ensures the filesystem reference remains valid regardless of the invocation context.