How the no-mistakes Git Hook Intercepts git push to Route Through the Gate Remote
The no-mistakes Git hook intercepts pushes by installing a post-receive script in a bare "gate" repository that resolves push metadata and invokes no-mistakes daemon notify-push to trigger the pipeline without blocking the push operation.
The kunchenguid/no-mistakes tool creates a dedicated gate — a bare Git repository that acts as the pipeline’s entry point. When you push to the remote named no-mistakes, Git fires a custom post-receive hook that captures the push details and forwards them to the daemon, ensuring every commit runs through your review and CI pipeline before reaching the main repository.
The Gate Remote Architecture
no-mistakes establishes a gate remote that points your working copy to a local bare repository. This remote, always named no-mistakes, serves as the interception point.
The setup is orchestrated by functions in internal/gate/gate.go:
ensureWorkingRemote(lines 19–21) adds theno-mistakesremote to your working repository if it does not exist, or repairs it during refresh operations.InitandInitWithFork(lines 28–38) create the bare gate repository under$NM_HOME/repos/<repo-id>.git, install the hook, and wire up the remote.
This architecture guarantees idempotency — running init multiple times only adds the remote when missing, never overwriting user-managed remotes.
The Post-Receive Hook Mechanism
When you execute git push no-mistakes <branch>, Git updates the bare gate repository and triggers the post-receive hook installed at <gate>/.git/hooks/post-receive. The hook is generated by PostReceiveHookScript in internal/git/hook.go (lines 15–89).
The hook operates non-blocking; Git ignores its exit status, meaning your push succeeds even if the pipeline daemon fails to start.
Resolving the Gate Directory
The hook must reliably locate the gate directory even when the current working directory collapses to . (issue #269). It uses:
GATE_DIR=$(git rev-parse --absolute-git-dir 2>/dev/null || pwd)
This guarantees an absolute path to the bare repository, ensuring the daemon always receives a valid --gate argument regardless of how Git invokes the hook.
Collecting Push Metadata
The hook parses the push data from Git’s standard input and environment variables. It collects:
- Old SHA (
$oldrev) and new SHA ($newrev) - Reference name (
$refname) - Push options from
GIT_PUSH_OPTION_COUNTandGIT_PUSH_OPTION_nvariables
The script builds the argument list incrementally:
set -- --gate "$GATE_DIR" \
--ref "$refname" \
--old "$oldrev" \
--new "$newrev"
# Append any push-options
while [ "$i" -lt "${GIT_PUSH_OPTION_COUNT:-0}" ]; do
opt=$(printenv "GIT_PUSH_OPTION_$i" 2>/dev/null || :)
set -- "$@" --push-option "$opt"
i=$((i + 1))
done
Invoking the Daemon
With arguments assembled, the hook invokes the daemon:
out=$(NM_HOOK_HELPER=1 "$NM_BIN" daemon notify-push "$@" 2>&1)
status=$?
The NM_HOOK_HELPER=1 environment variable signals to the binary that it is running from a hook context, disabling interactive UI elements. The daemon notify-push command (registered in internal/daemon/daemon.go) receives the gate path, ref names, and options to create a new pipeline run.
If the daemon exits non-zero, the hook logs the failure to $GATE_DIR/notify-push.log (lines 64–66 in hook.go) and echoes the error to stderr so the pushing client sees it. On success, it prints a concise ASCII banner indicating the pipeline has started.
Step-by-Step Push Interception Flow
-
Initialization:
no-mistakes initcreates the bare gate, installs the post-receive hook viagit.InstallPostReceiveHook, and adds theno-mistakesremote viaensureWorkingRemote. -
Push Execution: You run
git push no-mistakes HEAD:refs/heads/feature. Git contacts the bare gate and updates the ref. -
Hook Fires: The post-receive script resolves
$GATE_DIRusinggit rev-parse --absolute-git-dir. -
Metadata Extraction: The hook reads the old SHA, new SHA, and ref name from Git’s hook input, then iterates through
GIT_PUSH_OPTION_*variables to capture push options. -
Daemon Notification: The hook executes
no-mistakes daemon notify-pushwith the--gate,--ref,--old,--new, and--push-optionflags. -
Error Handling: Failures are written to
notify-push.loginside the gate directory and printed to stderr. The hook always exits cleanly to avoid blocking the Git operation. -
Pipeline Start: The daemon creates a new run in the database, beginning the review → test → lint pipeline using the gate’s bare repository as the source of truth.
Why the Gate Remote Is Essential
The gate remote provides three critical guarantees:
- Isolation: The gate is a bare repository; its history never contains unreviewed developer commits, ensuring the pipeline always validates clean states.
- Deterministic Source: All pipeline steps read from the gate’s remote-tracking refs, guaranteeing they process the exact commit the daemon received.
- Non-Destructive Setup: The
ensureWorkingRemotelogic only modifies theno-mistakesremote, leaving youroriginand other remotes untouched.
Usage Example
Initialize the gate and push a feature branch:
# Initialize the gate (creates bare repo, installs hook, adds remote)
$ no-mistakes init
# Create and commit a feature
$ git checkout -b feat/optimize
$ echo "optimized code" > main.go
$ git add main.go && git commit -m "optimize performance"
# Push to the gate remote
$ git push no-mistakes HEAD:refs/heads/feat/optimize
The hook outputs a banner confirming the intercept:
_ _ ____ _ _ _ ____ ___ ____ _ _ ____ ____
...
* Pipeline started
Run no-mistakes to review.
If the daemon fails to start, check the persistent log:
$ cat ~/.no-mistakes/repos/<repo-id>.git/notify-push.log
Summary
- The gate remote (
no-mistakes) routes pushes through a bare repository that acts as the pipeline entry point. - The post-receive hook in
internal/git/hook.gointercepts pushes, resolves the absolute gate path, and forwards metadata to the daemon. - Non-blocking execution ensures Git pushes succeed regardless of pipeline daemon status.
- Push metadata including SHAs, ref names, and push options are passed via
daemon notify-pusharguments. - Error logs persist in
notify-push.loginside the gate directory for debugging daemon connectivity issues.
Frequently Asked Questions
Does the hook block my push if the pipeline fails?
No. The hook operates non-blocking, meaning Git ignores the exit status of the post-receive script. Your push succeeds immediately, and any pipeline failures are logged to notify-push.log inside the gate directory while the error message is echoed to your terminal.
Where does the hook log errors when the daemon is unreachable?
The hook writes failures to $GATE_DIR/notify-push.log (as implemented in hook.go lines 64–66), where $GATE_DIR is the absolute path to the bare gate repository. You can view this log to diagnose why the no-mistakes daemon notify-push command failed.
What is the NM_HOOK_HELPER=1 environment variable for?
This variable tells the no-mistakes binary that it is being invoked from a Git hook rather than an interactive terminal. When set, the CLI disables interactive UI elements and streams output appropriately for the hook context, as seen in the hook script generated by PostReceiveHookScript.
How does the hook handle Git push options?
The hook iterates through the GIT_PUSH_OPTION_COUNT and GIT_PUSH_OPTION_n environment variables (where n is an index), appending each option to the daemon command via --push-option flags. This allows the pipeline to receive additional metadata, such as CI configuration flags, passed during git push --push-option.
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 →