How the no-mistakes Post-Receive Hook Resolves Paths in Bare Repositories
The post-receive hook generated by no-mistakes uses a two-step resolution strategy that first attempts git rev-parse --absolute-git-dir and falls back to deriving the path from the hook's own location ($0) to ensure the daemon always receives an absolute path to the bare repository.
The no-mistakes CI/CD system generates a custom post-receive hook for bare repositories (called "gates") that must reliably determine its own absolute path before notifying the daemon. Because Git may invoke hooks with a working directory of . that the daemon rejects (see issue #269), the hook implements robust path resolution logic in internal/git/hook.go to guarantee a fully-qualified gate directory.
Why Absolute Path Resolution Matters
When Git executes a post-receive hook, the current working directory (PWD) is often set to the repository root or even ., which resolves to a relative path. The no-mistakes daemon strictly requires an absolute path via the --gate argument to avoid the "invalid gate path: ." error that previously blocked pipelines. According to the source code in internal/git/hook.go, the hook must therefore resolve the bare repository's location before invoking the daemon manager in internal/daemon/manager.go.
The Two-Step Path Resolution Strategy
The hook implements a cascading resolution strategy in internal/git/hook.go that combines Git's native capabilities with portable shell-based path manipulation. This approach is tested in internal/git/hook_test.go, which verifies the fallback behavior when git rev-parse is unavailable.
Step 1: Prefer Git’s Native Absolute Directory
The hook first attempts to use Git's built-in path resolution, available since Git 2.13:
GATE_DIR=$(git rev-parse --absolute-git-dir 2>/dev/null || :)
The git rev-parse --absolute-git-dir command returns the absolute path of the .git directory. If this succeeds, GATE_DIR contains a fully-qualified path and the hook proceeds directly to execution.
Step 2: Fallback to the Hook’s Own Location
If the Git command fails (older Git versions or restricted environments), the script calculates the path from the hook file's location:
case "$GATE_DIR" in
/*) ;; # already absolute – keep it
*)
HOOK_PATH=$0
case "$HOOK_PATH" in
*/*) HOOK_DIR=${HOOK_PATH%/*} ;;
*) HOOK_DIR=. ;;
esac
GATE_DIR=$(cd "$HOOK_DIR/.." 2>/dev/null && (/bin/pwd -P 2>/dev/null || pwd -P) || :)
;;
esac
This logic extracts the directory containing the hook (HOOK_DIR) from $0, moves up one level (..) to reach the bare repository root, and resolves symlinks using pwd -P (or /bin/pwd -P for portability).
Step 3: Guarantee an Absolute Result
A final safety net ensures the path is absolute even if previous steps failed:
case "$GATE_DIR" in
/*) ;; # already absolute
*) GATE_DIR=$(/bin/pwd -P 2>/dev/null || pwd -P 2>/dev/null || pwd) ;;
esac
This normalization prevents any relative path from reaching the daemon.
Passing the Resolved Path to the Daemon
Once resolved, the absolute GATE_DIR is passed as the --gate argument:
set -- --gate "$GATE_DIR" \
--ref "$refname" \
--old "$oldrev" \
--new "$newrev"
The daemon defined in internal/daemon/manager.go receives this reliable path and uses it to start the appropriate pipeline, eliminating the path resolution failures that previously stopped automation.
Installing and Debugging the Hook
When initializing a gate (handled in internal/gate/gate.go), the hook is installed via the Go API:
// Install the hook (normally done by `no-mistakes init`)
if err := git.InstallPostReceiveHook(bareRepoPath); err != nil {
log.Fatalf("failed to install post‑receive hook: %v", err)
}
For debugging, you can manually trigger the hook from any working directory:
# Assume we are inside a worktree that points to a bare gate at /tmp/gate.git
cd /tmp/my-worktree
# The hook will still resolve the correct gate directory:
./hooks/post-receive
# Internally the script will set GATE_DIR=/tmp/gate.git
Summary
- The post-receive hook in
no-mistakesmust resolve absolute paths to avoid "invalid gate path" errors that prevent pipeline execution. - Primary resolution uses
git rev-parse --absolute-git-dir(Git 2.13+) for reliable absolute path detection. - Fallback resolution derives the path from
$0(the hook's location) using shell parameter expansion andpwd -Pto handle older Git versions. - A final safety net guarantees absolute paths before passing
--gateto the daemon ininternal/daemon/manager.go. - This two-step strategy ensures compatibility across Git versions and execution environments, including poisoned
PWDscenarios.
Frequently Asked Questions
What happens if git rev-parse --absolute-git-dir fails?
If the command fails (pre-Git 2.13 or restricted environments), the hook automatically falls back to calculating the path from its own location using $0 and pwd -P, ensuring the daemon still receives a valid absolute path to the bare repository.
Why does the hook avoid using PWD for path resolution?
Git may invoke the hook with PWD set to . or a relative path, which the daemon rejects with an "invalid gate path" error (issue #269). The hook specifically avoids relying on the working directory to ensure cross-platform reliability and prevent automation failures.
How does the daemon validate the gate path?
The daemon in internal/daemon/manager.go receives the --gate argument and validates that it is an absolute path to a bare repository before starting the pipeline, preventing execution errors from malformed or relative paths.
Which Git versions are supported by this resolution strategy?
The strategy supports all Git versions: modern versions (2.13+) use the optimized git rev-parse --absolute-git-dir path, while older versions rely on the portable shell fallback that uses pwd -P and $0 manipulation, ensuring broad compatibility.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →