Git Commit and Push as the Deploy Gate: The ABS Convention
Why the live ABS site only deploys when local/origin/github are on the same commit. What this convention prevents, what it doesn't, and the post-receive hook that enforces it.

The convention in one sentence
The live ABS site only updates after git push succeeds and local, origin, and github all carry the same commit SHA. This is “three-SHA match”: before each push, the operator (or a cron) confirms local == origin == github.
The convention exists because deploying without a recorded commit breaks the audit trail — when the site goes wrong 3 weeks later, you need to be able to point at a SHA and say “what we shipped was this.”
Why three-SHA match, not one-SHA
One-SHA match (just verify local is up to date) means you ship without a backup on GitHub. If the VPS root-disk dies after the deploy, you’ve lost the source of the live site.
Two-SHA match (local + origin) is the typical git workflow. But origin on a self-hosted bare repo doesn’t have the same durability as GitHub — gitfs on the same disk is one disk-failure away from gone.
Three-SHA match (local + origin + GitHub) is three independent backups of the live site. The probability of losing all three at once is ~0.
What the post-receive hook does
The hook at /srv/git/abs-site.git/hooks/post-receive is the actual gate. On git push origin main from /srv/abs-site it does, in order, with a shared MATRIX_STATE_LOCK held across all of it:
- Acquire the matrix state lock on fd
9from/opt/data/cron-state/matrix-abs-publish.lock. A concurrent daily cron queues behind the hook — serialised, not parallel. Released on trap EXIT, neverrm-ed (a queued waiter could still hold the inode). - Checkout + reset hard the working tree at
/srv/abs-siteto the just-pushed$newrev. If checkout fails, the hook aborts and never touches the live webroot. - Stage fresh R2 data by invoking the same
refresh_abs_from_matrix.shorchestrator the daily cron uses, inMATRIX_ABS_STAGE_ONLY=1mode withMATRIX_ABS_LOCK_HELD=1(so the child does not deadlock on the lock the parent already owns). If the orchestrator is missing, unreadable, or exits non-zero, the hook aborts beforenpm run buildruns. - Build the site with
npm run build. Build failure aborts. - Run the regression suite — the per-concern scripts under
/opt/data/scripts/(search bar, search filter, CSS, analytics backend, backend security, Cloudflare baseline, daily briefing, mobile nav, grade sort). First red light stops the deploy. Per Alastair’s standing rule, every previously-fixed bug is now a permanent test. - rsync
dist/to/var/www/agenticbotsitter/— this is the moment the live site actually changes. Only happens if every prior step exited 0. - Push the same commit to GitHub as
MarvinAi5/ABS. If this push fails, the hook logs and continues — local is what users see, GitHub can be reconciled later — but the next three-SHA check will catch the gap.
The key shape: any of those five failure points (checkout, stage, build, regression, rsync) hard-aborts before the live webroot is touched. There is no “deploy the committed snapshot” fallback. That’s deliberate — the fallback is exactly the consistency violation the hook is here to prevent.
What this convention prevents
- Deploying without a recorded commit. Every byte served from
/var/www/agenticbotsitter/is traceable to a SHA. - Live site drifting from the source-of-truth (origin). The hook is what reconciles them; nothing else writes to the webroot.
- Live site drifting from the durable mirror (GitHub). Step 7 forwards the commit; the three-SHA check in the next deploy verifies the mirror caught up.
- Pushing to origin and assuming GitHub synced. It doesn’t always — see the
abs-news-cron-pipelineorigin↔github gap. The check at deploy time is what makes the gap visible. - Race conditions where two deploys collide. The matrix state lock serialises the hook against the daily cron, and
git pushitself rejects non-fast-forwards by default.
What it doesn’t prevent
- A bad commit pushed to all three. The convention gates how a commit lands live, not whether the commit is correct. That’s what the regression suite (step 5) is for, and even the suite only catches previously-seen bug classes.
- Tunnel or origin failures. Those are caught by the watchdog sweep (cron-health-monitor,
cron-health-monitorskill), not the deploy gate. The deploy gate is downstream of those being up. - Cache stale views. Cloudflare’s edge can keep serving old bytes for hours after a successful rsync. A separate CF purge step (
cloudflare-edge-cache-purge) is required, and that’s its own concept.
The three-SHA check
LOCAL=$(git rev-parse HEAD)
ORIGN=$(git ls-remote origin refs/heads/main | cut -f1)
GITHUB=$(git ls-remote github refs/heads/main | cut -f1)
[ "$LOCAL" = "$ORIGN" ] && [ "$ORIGN" = "$GITHUB" ] && echo "OK"
If the assertion fails, the deploy chain should hard-fail. The post-receive hook already enforces this implicitly (a git push that fails step 7 will not produce a matching GITHUB SHA, and the next operator run will see the mismatch), but the explicit check is what you run before you trust the live site to be what you think it is.
What not to do
- Don’t push to origin and skip the github push — the next
git reset --hardon origin will lose it. The post-receive hook forwards automatically, but a manualgit push origin mainfrom the working tree does not. Always verify with the three-SHA check. - Don’t edit files in
/var/www/agenticbotsitter/directly — that bypasses the gate and the SHA trail. If you find yourself doing this, stop and put the change in a commit onmainso the hook can deploy it the right way. - Don’t accept a partial three-SHA match — partial means one of the three is lying, and you don’t know which. Re-run the push, check the hook log at
/opt/data/logs/abs-deploy.log, fix the broken leg, then re-verify.
Verification checklist
| # | Command | What it proves |
|---|---|---|
| 1 | git rev-parse HEAD | Local SHA known |
| 2 | git ls-remote origin refs/heads/main | cut -f1 | Origin SHA known |
| 3 | git ls-remote github refs/heads/main | cut -f1 | GitHub SHA known |
| 4 | The equality check above | All three match |
| 5 | Live site curl returns the new guide | The deploy actually took |
Step 5 closes the loop: even with three-SHA match, the deploy could be silently broken (a hook that runs but rsyncs an empty dist/, a tunnel that has the wrong origin, a Cloudflare cache that never got purged). The curl is what proves users are actually seeing the new SHA.
Sources
- Git push reference
- ABS companion: Three-SHA Check Before Pushing Live Webroot or GitHub
- ABS companion: ABS Regression Failure Modes
Last verified: 2026-07-14 against the live post-receive hook at /srv/git/abs-site.git/hooks/post-receive.



Submit a take
Have a different read on this? Drop a comment below — your email isn't published, and I read every one. Nothing leaves the site until I approve it.