v1.31.2 field report: post-receive hook sends `--gate .` (+ session-limit handling, error classification, trust preflight, default_skips)

Author: Blakeolson21Created Jul 5, 2026Updated Sep 21, 2026
Labelsbug

no-mistakes v1.31.2 — field report from a heavy user (bugs + feature requests)

Environment: macOS (Darwin 25.3), no-mistakes version v1.31.2 (3c09496) 2026-06-27T00:02:18Z — latest per update-check as of 2026-07-05. Two gated repos: Skill-Life (mirror ~/.no-mistakes/repos/fd24038ede32.git) and Remote-Comp (433465ced6fd.git). Agent backend is the Claude Code CLI. No source on this machine, so everything below is an upstream request; all evidence is from ~/.no-mistakes/state.sqlite, ~/.no-mistakes/logs/<ULID>/, logs/daemon.log, and repos/*/notify-push.log.

First off — the pipeline is doing real work here (~70 runs in the last day across two repos), and most of what follows is "it's so close to great that the rough edges stand out." Five items, each with symptom → evidence → root cause as best I can tell from the outside → requested change.


1. post-receive hook sends --gate . — daemon rejects it, and axi run silently falls back to a broken rerun

Symptom. no-mistakes axi run on a branch that has never had a run fails with no run started …: no previous run for branch <branch>. Branches with a prior run appear to work — which masked this for a while.

Evidence. Both mirrors' notify-push.log files show repeated hook failures (7 occurrences in each; sample from Remote-Comp's 433465ced6fd.git/notify-push.log):

[2026-07-04T20:27:28] notify-push failed for refs/heads/claude/relaxed-hypatia-cf135c (exit 1)
invalid gate path: .

logs/daemon.log shows the daemon-side rejection and, ~5s later, the client's fallback failing:

time=2026-07-04T20:27:28.494-05:00 level=INFO msg="ipc request failed" method=push_received error="invalid gate path: ."
time=2026-07-04T20:27:33.513-05:00 level=INFO msg="ipc request" method=rerun
time=2026-07-04T20:27:33.571-05:00 level=INFO msg="ipc request failed" method=rerun error="no previous run for branch claude/relaxed-hypatia-cf135c"

17 invalid gate path: . rejections and 26 failed rerun fallbacks in daemon.log across 2026-07-04/05.

Root cause. The hook template embedded in the binary uses logical pwd:

bash
LOG="$(pwd)/notify-push.log"
...
set -- --gate "$(pwd)" \

POSIX pwd (logical mode) trusts an inherited PWD environment variable when it "names" the cwd — and . always does. When the push comes from axi run (the Go client), the hook's shell inherits a stale/relative PWD, so the hook sends --gate . and the daemon rejects it. axi run then silently falls back to IPC rerun, which only works if the branch already had a run.

Local patch (verified end-to-end 2026-07-05) — derive the gate path from the hook's own location, immune to both inherited PWD and the daemon's cwd:

bash
GATE_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
LOG="$GATE_DIR/notify-push.log"
...
set -- --gate "$GATE_DIR" \

(--gate "$(pwd -P)" alone would also work; the $0-derived form is stricter.) After patching, the same branch that failed at 20:27:28 gated cleanly (run 01KWQY8DYD3FFX4MRKY936AZ7T, completed). Originals preserved as hooks/post-receive.bak-pwd-bug if you want a diff.

Asks.

  1. Fix the hook template to use pwd -P or the $0-derived gate path.
  2. Have axi run surface the notify-push failure (the hook's exit 1 and the daemon's invalid gate path are both known at that point) instead of silently falling back to rerun — the fallback turns a diagnosable config bug into a misleading "no previous run for branch" error, and on branches with history it hides the bug entirely.
  3. Note the patch is clobbered whenever a hook is regenerated (no-mistakes init, re-registering a gate, upgrades), so a template fix is the only durable one.

2. Claude session limits should be a first-class run state (pause/queue, not fail)

Symptom. On the night of 2026-07-04 (~21:30–22:40 CDT), six runs failed with step error agent <step>: claude exited: exit status 1: — nothing after the colon. The actual cause appears only in the step logs, and it's the same verbatim line in all six:

You've hit your session limit · resets 1:20am (America/Chicago)

Evidence. All six, from runs + step_results + logs/<ULID>/<step>.log (times CDT):

Run ULID Branch Created Failed step Session-limit line at
01KWR1RGPCZTBZ74DB6TTZDR8Z claude/zev-series-integration 07-04 21:29:46 test test.log
01KWR3Z40QAMD81NY6S204KB55 claude/ios-video-transport-switcher 07-04 22:08:19 test test.log:21
01KWR43TFHVZZW768Q90DD2Z04 claude/intelligent-merkle-7fee07 07-04 22:10:53 document document.log:3
01KWR5JZ6BB81NC2Y4141CM4P0 claude/festive-shannon-5772db 07-04 22:36:38 review review.log:3
01KWR5NW1VYBTS56NREABJTAFA claude/zev-series-integration 07-04 22:38:13 review review.log:3
01KWR5P2SY2J34H26AMJXYEF96 claude/festive-shannon-5772db 07-04 22:38:20 review review.log:3

Representative log (01KWR43TFHVZZW768Q90DD2Z04/document.log, entire file):

updating documentation...

You've hit your session limit · resets 1:20am (America/Chicago)
error: agent document: claude exited: exit status 1:

Note 01KWR3Z40QAM… is the painful case: the agent had already fixed and committed in an earlier round (committed agent fixes: no-mistakes(test): guard iOS-only receiver ref…), then the next claude invocation hit the limit and the whole run failed anyway.

Asks.

  1. Detect the session-limit string on the agent's stdout and record it as the run's failure reason (see item 3).
  2. Better: make it a distinct run state — the message includes the reset time, so the daemon could pause/queue the run and auto-resume after the stated reset instead of failing. Six runs died in ~70 minutes here, several mid-pipeline with work already committed; every one of them would have succeeded on retry after 1:20am.

3. Error classification: claude exited: exit status 1: with an empty tail covers at least three distinct causes

Symptom. The DB error field (both runs.error and step_results.error) is populated for all agent failures, but for most of them the string terminates at exit status 1: with nothing after the colon — the CLI captures the exit status but not the reason. Operators (and driver agents polling axi status) can't distinguish failure classes without opening per-step logs.

Evidence. Aggregate over all 11 claude exited step failures in the DB:

sql
SELECT COUNT(*), SUM(COALESCE(r.error,'')='') FROM step_results s
JOIN runs r ON r.id=s.run_id WHERE s.error LIKE '%claude exited%';
-- 11 total; 0 with empty error column — but 8 of the 11 tails are empty after "exit status 1:" / "signal: killed:"

The 11 break down into at least four signatures, only one of which currently reaches the DB:

  1. Session limit (6 runs, item 2) — tail empty in 4 of 6; reason only in the step log.

  2. End-of-run CLI crash after the work succeeded (2 runs, tail empty): 01KWS4WX7PD1FS9X4YPWP5H4Y1 (claude/great-golick-f885e5, 07-05 07:43:50) and 01KWS9SACT4337ZARPQRNS8S79 (claude/sharp-brahmagupta-ab6556, 07-05 09:09:15). In both, intent/rebase/review are completed and the agent's own log reports tests passing ("All 74 tests pass" / "All 72 voice/audio tests pass") — then claude exits 1 at the very end. 01KWS9SACT's test.log ends:

    ...The worktree is clean (`node_modules` is gitignored, no stray files).Let me retry with simplified ASCII content to avoid any parsing issue.
    error: agent run tests: claude exited: exit status 1:

    which looks like a crash during final-output/artifact generation (01KWS4WX7P ends the same way minus the retry text). A green run failing at the finish line with an empty error is the most confusing case in the whole set.

  3. Workspace trust (2 runs, item 4) — the only class where the real reason reaches the DB, because it happens to be on stderr.

  4. signal: killed (3 further steps, empty tail: runs 01KWR3BQE60D…, 01KWR3VRHWG3…, 01KWR3WA1A59…) — presumably OOM/kill, again indistinguishable in the DB.

Asks.

  1. Capture the last N relevant lines of the step's stdout/stderr into step_results.error (and roll up to runs.error) whenever the agent exits non-zero — the truncation at exit status 1: suggests only stderr is captured, and claude prints the interesting failures to stdout.
  2. Classify known signatures into a machine-readable field: session_limit (+ parsed reset time), workspace_untrusted, killed, agent_crash_after_success. axi status could then show why at a glance, and automation (like my driver agents) could branch on it.

4. Workspace-trust preflight for repo mirrors

Symptom. Review/test steps run claude inside the bare mirror (~/.no-mistakes/repos/<hash>.git). If ~/.claude.json lacks hasTrustDialogAccepted: true for that exact path, the claude CLI refuses the workspace's permission entries and the step dies exit-1.

Evidence. Runs 01KWR1RGPCZTBZ74DB6TTZDR8Z (test step) and 01KWR5JZ6BB81NC2Y4141CM4P0 (review step), both 2026-07-04 against the Skill-Life mirror. Verbatim from 01KWR5JZ6B…/review.log (entire file):

reviewing changes...

You've hit your session limit · resets 1:20am (America/Chicago)
error: agent review: claude exited: exit status 1: Ignoring 511 permissions.allow entries from .claude/settings.json: this workspace has not been trusted. Run Claude Code interactively here once and accept the trust dialog, or set projects["~/.no-mistakes/repos/fd24038ede32.git"].hasTrustDialogAccepted: true in ~/.claude.json.

(home-directory prefix abbreviated to ~ in the quote above; otherwise verbatim)

(These two runs overlapped with the session-limit window, so each shows both signals — the limit on stdout, the trust warning on stderr. The trust warning is real and independently reproducible: until I set the flag manually for each mirror, every agent step in that mirror inherited zero allow-list entries.)

Ask. Have no-mistakes doctor — and ideally run-create — preflight-check ~/.claude.json for hasTrustDialogAccepted: true on every registered mirror path, and either warn with the exact fix or (with consent) write the flag itself. It's a one-line JSON edit that currently has to be discovered from a mid-run stderr message. New mirrors get created by init, so each new gated repo re-triggers this.


5. Hook-started runs ignore the owner's standing --skip push,pr,ci policy

Symptom. My shipping policy attaches --skip push,pr,ci to every manual axi run. Runs started by the git-push hook can't take flags, and they always drive push→PR→CI — so the same branch gets a fully-compliant gate when I start it and a policy-violating one when a plain git push to the gate remote starts it.

Evidence. 69 pipeline runs on 2026-07-05 UTC (73 rows minus 4 pending shells with no steps). Hook-started runs are identifiable as intent_source='claude' (they carry an intent_session_id); manual axi run runs are intent_source='agent':

sql
SELECT r.intent_source, s.step_name, s.status, COUNT(*) FROM step_results s
JOIN runs r ON r.id=s.run_id WHERE s.step_name IN ('push','pr','ci')
GROUP BY 1,2,3;
intent_source push/pr/ci skipped rows push/pr/ci executed (terminal)
agent (manual, 60 runs) 37 each of push/pr/ci 3 push, 3 pr, 1–2 ci
claude (hook, 9 runs) 0 7 push, 7 pr, 6 ci (rest pending/running)

Every hook-started run that reached the push step executed it; zero ever skipped. Manual runs skipped push/pr/ci in 37 of 60. Concrete pair, identical 9-step pipeline, ~12 minutes apart on 2026-07-04:

  • Hook-started 01KWR2BGJ88APW0A48WMBPQ7SD (claude/peaceful-neumann-6f0a2a, 21:40:08 CDT): steps 7–9 push/pr/ci all completed.
  • Manual 01KWR318C4RGC7V24TSN3GEVXN (claude/brave-taussig-df010c, 21:52:01 CDT, intent contains --skip push,pr,ci): steps 7–9 all skipped.

Ask. A config-level default_skips (per repo, in the gate config) that hook-started runs inherit — or failing that, have the hook-started run inherit the skips from the branch's (or repo's) most recent manually-created run. Either would let the hook path respect the repo owner's shipping policy; today the only safe options are "never plain-push to the gate remote" or per-run vigilance.


Summary of asks

  1. Hook template: pwd -P / $0-derived gate path; axi run surfaces notify-push failure instead of silent rerun fallback.
  2. Session limit → first-class state: recorded reason, ideally pause-and-auto-resume at the stated reset time.
  3. Non-zero agent exits: capture last-N log lines into the DB error, classify known signatures (session limit, trust, killed, end-of-run crash).
  4. doctor/run-create preflight for claude workspace trust on every mirror.
  5. default_skips (or inherited skips) for hook-started runs.

Happy to share any of the referenced logs/DB rows in full, or test a pre-release build against this setup — the two-repo, high-volume, agent-driven usage here seems to exercise paths that lighter setups don't.