Skip to content

Commit e21173a

Browse files
committed
rules: stop hand-typing how long ze-verify takes
Four sites told an agent to run make ze-verify with a 240s timeout, while other paragraphs of the same rule said 25 to 30 minutes and half an hour, ai/rules/testing.md said 4-10 minutes, and scripts/dev/verify-lock.sh said ~2 min with MAX_LOCK_AGE 1800s. 240s was the only one written as a directive and the one no producer supports. An agent that obeyed it killed a healthy run and read the result as a red tree. The rule now names no timeout. It says run it in the foreground, wait, never poll, do not kill it for being slow, give the call the largest timeout your harness allows, and take the duration from tmp/.ze-verify-duration.txt, which _record_duration in verify-lock.sh actually writes. It quotes the corpus's two competing figures rather than pretending they are absent. Each directive is one physical line. condense_body in scripts/dev/rules_condensed.py appends the bold-led LINE raw, so a directive that wraps reaches ai/rules/CORE.md cut mid-clause. The always-on digest now carries all four whole. The migration is completed rather than half-landed: ai/skills/ze-verify.md was the operative instruction at the point of use and still banned run_in_background while citing a rule section that does not exist, and still told agents to abandon the pass at 240s. ai/skills/ze-check.md, ai/rationale/git-safety.md and a hook comment carried the same number. ai/rationale/git-safety.md also carved out a backgrounding exception that contradicted the rule it exists to explain. A stale 22-stage count in a commands point file is fixed the same way, by naming no count. Five independent reviewers over three rounds found ten defects in the first two drafts of this change, including that the fix had introduced 600s with no producer and cited MAX_LOCK_AGE from the opposite premise. Round 3 closed at 0 blocker, 0 issue. plan/learned/1359 records that, because the failure it documents caught the person writing the rule against it, three times.
1 parent 4e76bb2 commit e21173a

15 files changed

Lines changed: 254 additions & 35 deletions

.claude/hooks/pretool-bash.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,12 @@ def check_root_build(cmd, _ctx):
183183
# Options that take a SEPARATE argument (`timeout -k 5 30 make ...`,
184184
# `nice -n 5 make ...`): the flag and its value both sit in front.
185185
LAUNCHER_OPT_WITH_ARG = {"-k", "--kill-after", "-s", "--signal", "-n", "--adjustment"}
186-
# The duration/niceness operand itself. `timeout` accepts a unit suffix, and
187-
# ai/rules/git-safety.md tells every session to write exactly that (`timeout 240s
188-
# make ze-verify`) -- the old bare-`isdigit()` test did not match it, so the
189-
# repo's own documented invocation slipped straight past this gate. A bare
186+
# The duration/niceness operand itself. `timeout` accepts a unit suffix and
187+
# sessions write one (`timeout <n>s make ze-verify`) -- the old bare-`isdigit()`
188+
# test did not match it, so that invocation slipped straight past this gate.
189+
# No duration is named here on purpose: ai/rules/git-safety.md ("Running
190+
# ze-verify") sets the policy, and a number copied into a comment is the drift
191+
# this repo keeps paying for (plan/learned/1359). A bare
190192
# negative niceness (`nice -5 make ...`) is a flag and the operand at once, hence
191193
# the optional leading `-`.
192194
LAUNCHER_OPERAND = re.compile(r"^-?\d+(?:\.\d+)?[smhd]?$")

ai/LEARNED-FULL-INDEX.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ reading: `ai/LEARNED-INDEX.md`. Meta-summaries: `DESIGN-HISTORY.md`
88
(why the code is as it is), `RECURRING-PATTERNS.md` (known traps),
99
`HOOK-FRICTION.md` (hook false positives).
1010

11-
Total: 933 summaries
11+
Total: 934 summaries
1212

1313
## 400-499
1414

@@ -992,3 +992,4 @@ Total: 933 summaries
992992
| 1356 | Learned Corpus: Drain the Ceiling, Do Not Delete the Gate | `plan/learned/1356-learned-corpus-drain-over-archive.md` |
993993
| 1357 | `flock(1)` on a macOS dev machine is not util-linux | `plan/learned/1357-flock-macos-is-not-util-linux.md` |
994994
| 1358 | dev-setup-cross-platform | `plan/learned/1358-dev-setup-cross-platform.md` |
995+
| 1359 | A number typed into a rule file is an unverified claim | `plan/learned/1359-rules-corpus-paraphrase-drift.md` |

ai/rationale/git-safety.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@ tea issue create --title "..."
2626

2727
## Why `ze-verify` Runs Foreground
2828

29-
`ze-verify` finishes well under the 240s Bash timeout in normal cases
30-
(two-pass strategy: cached full pass + `-race` only on changed groups).
3129
Running foreground means the tool result IS the completion signal -- no
32-
polling, no missed notifications, log is ready to read on return.
30+
polling, no missed notifications, log is ready to read on return. The
31+
two-pass strategy (cached full pass + `-race` only on changed groups) is
32+
what keeps the common case short enough for that to be practical. How
33+
short is not stated here: `_record_duration` (`scripts/dev/verify-lock.sh`)
34+
appends the real elapsed seconds to `tmp/.ze-verify-duration.txt`, and a
35+
duration typed into a document is a claim
36+
(`plan/learned/1359-rules-corpus-paraphrase-drift.md`).
3337

3438
If a previous run is still going, `verify-lock.sh` blocks the second
3539
invocation inside the same foreground Bash call until the lock releases.
@@ -42,8 +46,11 @@ Anti-patterns that look like "smart" backgrounding but break:
4246
| `run_in_background: true` + `stat -c %Y` mtime check on `tmp/ze-verify.log` | Log is written continuously during the run; mtime never "settles" reliably |
4347
| `run_in_background: true` then assume you'll be notified | You will be, but a concurrent polling/sleep loop in Bash can swallow the notification |
4448

45-
Legitimate reasons to background `ze-verify`:
46-
- Genuinely independent work to do for >60s while it runs (rare).
47-
48-
In both cases: launch with `run_in_background: true` and **stop**. No
49-
polling loop. Ever.
49+
There is no legitimate reason to background it. `ai/rules/git-safety.md`
50+
("Running ze-verify") says foreground, wait, never poll, and `ai/skills/ze-verify.md`
51+
step 2 says the same. This paragraph used to carve out an exception for
52+
"genuinely independent work to do for >60s while it runs", contradicting the
53+
rule it exists to explain, and enumerating "both cases" under a single bullet.
54+
Editing the tree during a run is what makes that exception unsafe: the gate
55+
reads the working tree, so independent work invalidates the run it is waiting
56+
for.

ai/rules/CORE.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ BLOCKING only when the commit could plausibly affect build, tests, or generated
7070
| Anything that runs at build time or affects a binary | YES |
7171
| `ai/**/*.md`, `.claude/**/*.md`, `plan/**/*.md`, `docs/**/*.md`, `README.md` | NO |
7272
### Step 1: If `ze-verify` applies (BLOCKING)
73-
`make ze-verify` (timeout 240s).
73+
`make ze-verify`, in the foreground ("Running ze-verify" below).
7474
### Structural Gates Are Never Known-Red (BLOCKING)
7575
The item-2 "log to `plan/known-failures/`" path is for **non-deterministic** failures only -- flaky or environmental TEST reds (load-sensitive races, GC-pressure pool flakes, host-specific listener probes).
7676
**The general escape is owner-only: `--structural-red-ok "<reason>"`** (the
@@ -110,7 +110,11 @@ One `make ze-verify*` (or `ze-chaos-verify`) at a time repo-wide -- parallel run
110110
| If the run is yours (same tree), read `tmp/ze-verify.log` instead of re-running | Delete the lockfile |
111111
| If "waiting for lock" appears, do other work | Start `go test` / `golangci-lint` / `bin/ze-test` in parallel (bypasses lock) |
112112
### Running ze-verify
113-
Foreground with 240s timeout.
113+
Each directive below is one physical line on purpose.
114+
**Run `make ze-verify` in the foreground, wait for it, and never poll: the foreground return IS the completion signal.**
115+
**Do not kill it for being slow. Give the call the largest timeout your harness allows.**
116+
**Never take a timeout from a duration written in a rule. The one measurement is `tmp/.ze-verify-duration.txt`.**
117+
**Never edit the tree while a verify runs, yours or anybody's: it reads the working tree.**
114118
### A SHARED CHECKOUT NEVER GIVES A CLEAN `ze-verify` (BLOCKING)
115119
**Several agents work this checkout at once. `make ze-verify` reads the WORKING
116120
TREE, so it reads their half-finished edits too, and a fully green run is unreachable by construction.

ai/rules/commands.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ loops make it quadratic.
261261
### Poll cost
262262

263263
On 2026-08-02 a session left four `until ! pgrep ...; do sleep 5; done` loops
264-
running on a machine that was also running QEMU, Docker and a 22-stage
264+
running on a machine that was also running QEMU, Docker and a full
265265
`ze-verify`. The loops were started because a foreground `sleep` is refused, and
266266
they were never stopped when the thing they watched changed. The wake-ups were
267267
the contention that made the functional suites flaky for the rest of that

ai/rules/git-safety.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,8 @@ No = skip and note in commit summary. Unsure = run.
249249

250250
### Step 1: If `ze-verify` applies (BLOCKING)
251251

252-
`make ze-verify` (timeout 240s). Not `go test`, not any subset.
252+
`make ze-verify`, in the foreground ("Running ze-verify" below). Not `go test`,
253+
not any subset.
253254
Before any verify target, check freshness. A FRESH status covers the
254255
byte-identical tree and forbids rerunning `make ze-verify` or
255256
`make ze-verify-changed`. The check output is qualified by mode:
@@ -271,7 +272,7 @@ under `tmp/verify/`, `tmp/ze-verify-failures.log`,
271272

272273
```
273274
[ ] 0. `scripts/dev/verify-status.sh check`. FRESH -> MUST NOT run `make ze-verify` or `make ze-verify-changed` again; note timestamp. STALE -> continue only if the table above says verification applies.
274-
[ ] 1. `make ze-verify` (240s) only when status is STALE and the table above says YES. On failure read `tmp/ze-verify-failures.log` FIRST, choose a stage-local group, then open that group's `tmp/verify/<nn>-<stage>.log`.
275+
[ ] 1. `make ze-verify` (foreground, largest timeout your harness allows, never killed early) only when status is STALE and the table above says YES. On failure read `tmp/ze-verify-failures.log` FIRST, choose a stage-local group, then open that group's `tmp/verify/<nn>-<stage>.log`.
275276
[ ] 2. Failure from current work: fix + re-run. Pre-existing: fix after primary task in separate commit; if >10 min, log to `plan/known-failures/` (one `<make-target>-<test-name>.md` shard per failure).
276277
```
277278

@@ -479,8 +480,27 @@ PID-backed -- no cleanup after a crash.
479480

480481
### Running ze-verify
481482

482-
Foreground with 240s timeout. No background execution, no polling
483-
loops. Wait for completion.
483+
Each directive below is one physical line on purpose. `condense_body`
484+
(`scripts/dev/rules_condensed.py`) emits a bold-led LINE raw into
485+
`ai/rules/CORE.md`, so an instruction that wraps arrives there cut in half.
486+
487+
**Run `make ze-verify` in the foreground, wait for it, and never poll: the foreground return IS the completion signal.**
488+
No background run, no sleep-and-check loop, no `tail` on a log that is still
489+
growing.
490+
491+
**Do not kill it for being slow. Give the call the largest timeout your harness allows.**
492+
A verify that is still running is not a verify that is hung, and killing one
493+
costs the whole pass rather than the seconds it saves.
494+
495+
**Never take a timeout from a duration written in a rule. The one measurement is `tmp/.ze-verify-duration.txt`.**
496+
This corpus disagrees with itself about how long a full pass takes: "25 to 30
497+
minutes" below, "4-10 minutes" in `ai/rules/testing.md`. Both were typed by
498+
hand. `_record_duration` (`scripts/dev/verify-lock.sh`) appends the real elapsed
499+
seconds to that file, and when it is absent nothing here has measured it
500+
(`plan/learned/1359-rules-corpus-paraphrase-drift.md`).
501+
502+
**Never edit the tree while a verify runs, yours or anybody's: it reads the working tree.**
503+
An edit mid-run invalidates the run you are waiting for.
484504

485505
### A SHARED CHECKOUT NEVER GIVES A CLEAN `ze-verify` (BLOCKING)
486506

ai/rules/points/commands/rationale/abandoned-poll-loops-made-the-suites-flaky.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ level:
44
stage:
55
---
66
On 2026-08-02 a session left four `until ! pgrep ...; do sleep 5; done` loops
7-
running on a machine that was also running QEMU, Docker and a 22-stage
7+
running on a machine that was also running QEMU, Docker and a full
88
`ze-verify`. The loops were started because a foreground `sleep` is refused, and
99
they were never stopped when the thing they watched changed. The wake-ups were
1010
the contention that made the functional suites flaky for the rest of that

ai/rules/points/git-safety/before-any-commit/run-make-ze-verify-and-check-freshness-first.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ kind: note
33
level:
44
stage:
55
---
6-
`make ze-verify` (timeout 240s). Not `go test`, not any subset.
6+
`make ze-verify`, in the foreground ("Running ze-verify" below). Not `go test`,
7+
not any subset.
78
Before any verify target, check freshness. A FRESH status covers the
89
byte-identical tree and forbids rerunning `make ze-verify` or
910
`make ze-verify-changed`. The check output is qualified by mode:

ai/rules/points/git-safety/before-any-commit/run-verify-in-the-foreground-and-wait.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,24 @@ kind: note
33
level:
44
stage:
55
---
6-
Foreground with 240s timeout. No background execution, no polling
7-
loops. Wait for completion.
6+
Each directive below is one physical line on purpose. `condense_body`
7+
(`scripts/dev/rules_condensed.py`) emits a bold-led LINE raw into
8+
`ai/rules/CORE.md`, so an instruction that wraps arrives there cut in half.
9+
10+
**Run `make ze-verify` in the foreground, wait for it, and never poll: the foreground return IS the completion signal.**
11+
No background run, no sleep-and-check loop, no `tail` on a log that is still
12+
growing.
13+
14+
**Do not kill it for being slow. Give the call the largest timeout your harness allows.**
15+
A verify that is still running is not a verify that is hung, and killing one
16+
costs the whole pass rather than the seconds it saves.
17+
18+
**Never take a timeout from a duration written in a rule. The one measurement is `tmp/.ze-verify-duration.txt`.**
19+
This corpus disagrees with itself about how long a full pass takes: "25 to 30
20+
minutes" below, "4-10 minutes" in `ai/rules/testing.md`. Both were typed by
21+
hand. `_record_duration` (`scripts/dev/verify-lock.sh`) appends the real elapsed
22+
seconds to that file, and when it is absent nothing here has measured it
23+
(`plan/learned/1359-rules-corpus-paraphrase-drift.md`).
24+
25+
**Never edit the tree while a verify runs, yours or anybody's: it reads the working tree.**
26+
An edit mid-run invalidates the run you are waiting for.

ai/rules/points/git-safety/before-any-commit/the-pre-commit-verify-checklist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ stage:
55
---
66
```
77
[ ] 0. `scripts/dev/verify-status.sh check`. FRESH -> MUST NOT run `make ze-verify` or `make ze-verify-changed` again; note timestamp. STALE -> continue only if the table above says verification applies.
8-
[ ] 1. `make ze-verify` (240s) only when status is STALE and the table above says YES. On failure read `tmp/ze-verify-failures.log` FIRST, choose a stage-local group, then open that group's `tmp/verify/<nn>-<stage>.log`.
8+
[ ] 1. `make ze-verify` (foreground, largest timeout your harness allows, never killed early) only when status is STALE and the table above says YES. On failure read `tmp/ze-verify-failures.log` FIRST, choose a stage-local group, then open that group's `tmp/verify/<nn>-<stage>.log`.
99
[ ] 2. Failure from current work: fix + re-run. Pre-existing: fix after primary task in separate commit; if >10 min, log to `plan/known-failures/` (one `<make-target>-<test-name>.md` shard per failure).
1010
```

0 commit comments

Comments
 (0)