v1.31.2 field report: post-receive hook sends `--gate .` (+ session-limit handling, error classification, trust preflight, default_skips)
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:
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:
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.
- Fix the hook template to use
pwd -Por the$0-derived gate path. - Have
axi runsurface the notify-push failure (the hook's exit 1 and the daemon'sinvalid gate pathare both known at that point) instead of silently falling back torerun— 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. - 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.
- Detect the session-limit string on the agent's stdout and record it as the run's failure reason (see item 3).
- 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:
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:
Session limit (6 runs, item 2) — tail empty in 4 of 6; reason only in the step log.
End-of-run CLI crash after the work succeeded (2 runs, tail empty):
01KWS4WX7PD1FS9X4YPWP5H4Y1(claude/great-golick-f885e5, 07-05 07:43:50) and01KWS9SACT4337ZARPQRNS8S79(claude/sharp-brahmagupta-ab6556, 07-05 09:09:15). In both, intent/rebase/review arecompletedand 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 (
01KWS4WX7Pends 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.Workspace trust (2 runs, item 4) — the only class where the real reason reaches the DB, because it happens to be on stderr.
signal: killed(3 further steps, empty tail: runs01KWR3BQE60D…,01KWR3VRHWG3…,01KWR3WA1A59…) — presumably OOM/kill, again indistinguishable in the DB.
Asks.
- Capture the last N relevant lines of the step's stdout/stderr into
step_results.error(and roll up toruns.error) whenever the agent exits non-zero — the truncation atexit status 1:suggests only stderr is captured, and claude prints the interesting failures to stdout. - Classify known signatures into a machine-readable field:
session_limit(+ parsed reset time),workspace_untrusted,killed,agent_crash_after_success.axi statuscould 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':
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 allcompleted. - Manual
01KWR318C4RGC7V24TSN3GEVXN(claude/brave-taussig-df010c, 21:52:01 CDT, intent contains--skip push,pr,ci): steps 7–9 allskipped.
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
- Hook template:
pwd -P/$0-derived gate path;axi runsurfaces notify-push failure instead of silentrerunfallback. - Session limit → first-class state: recorded reason, ideally pause-and-auto-resume at the stated reset time.
- Non-zero agent exits: capture last-N log lines into the DB error, classify known signatures (session limit, trust, killed, end-of-run crash).
doctor/run-create preflight for claude workspace trust on every mirror.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.
Source: kunchenguid/no-mistakes