Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions docs/retrospectives/2026-10-22-merges-329.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# 2026-10-22 post-merge check: public #328 and #329, private #40 to #43

Times are UTC. Sources: `git log` on `origin/master` and `private/master`,
`gh pr view`, `.git/harness/reviews.jsonl` and `windows.jsonl`, `pmset -g
log`, the main session's transcript (`3b8a12b2-…jsonl`, "line N") and
`.claude/current-task/session.md`. What `2026-10-22-night.md` (PR #330)
records is pointed to, not repeated: the idle tail, the refusal table, the
push redone under a later grant, the defaults missing from the morning
report, #327's missing review.

## 0. Duties owed

`python3 tools/duties.py` at the start: "post-merge check (1 merge since
the last retrospective, 2026-10-22 04:24 UTC: #329)". #329 merged at
04:43:10 (`gh pr view 329`); this check was spawned at 04:49:08: **6 min**.
The recap in the main checkout also lists "push 2 retrospective branches
(worktree-retro-1021e, worktree-worktree-retro-1022b)"; for
`worktree-retro-1021e` see finding 3: owed since 20:37:10 yesterday, **8 h
18 min** at 04:55, 55 min of it with Ola back.

## 1. Lead time

| PR | Built | Waited | Of the wait, a refused `gh pr create` | Total |
|---|---|---|---|---|
| #329 harness: verdict line, redo after review, amends after `cd` | 7 h 18 min (spawn 10:00 to record 17:36) | 11 h 06 min | **10 h 35 min** (17:37:33 to 04:12:56) | 18 h 43 min |
| #328 check of #326 | 24 min (16:49 to 17:13) | 11 h 29 min | **10 h 53 min** (17:20:13 to 04:12:51) | 11 h 54 min |
| private #43 forcing evidence | 7 h 06 min (10:50 to 17:56) | 10 h 36 min | **8 h 03 min** (20:09:35 to 04:13:01) | 17 h 42 min |
| private #40 sun evaluator (89) | 2 h 23 min (design 01:18 to 03:41) | 50 min | none; Ola away to 04:00 | 3 h 13 min |
| private #41 snow-cover check | 4 min | 3 h 35 min | none; Ola away | 3 h 39 min |
| private #42 ideas note | 2 min | 3 h 47 min | none; Ola away | 3 h 49 min |

`docs/retrospectives/lead_time.py 2026-10-21` agrees in decimal hours, but
counts from the first commit (#329: 10:18, not the 10:00 spawn) and dates
the merge from the queue's merge commit (04:28), 15 min before `mergedAt`.

#329's build: design and two design reviews 52 min; **red step 4 h 03 min**
(`@tester` spawned 10:52:28, red `4e45ea0f` 14:55:18, 235 lines added); green 78
min; 27 min from green to the code review's spawn; review 53 min. The Mac
slept on battery from 11:52 to 12:23 and a few minutes besides (`pmset -g
log`); the rest of the red step's length is not checked here.

Three PRs lost **29 h 31 min** together to one cause: the away grant's form
for `gh pr create` (finding 1).

## 2. Findings

1. **The refusal named the wrong cause, the main session passed on an
unchecked one, and no route could redo the act.** The three opens were
refused for a double-quoted `--title`; the grant takes only single quotes
(`tools/push_grant.py`, `unexpandable`). The refusal says "the grant takes
only one bare command, every word written out (no variable, no &&, ;,
pipe, redirect or heredoc)" (line 20368): nothing about quotes. Probe,
on `origin/master` after #329: `unexpandable` returns False for `--title
"A b"` and True for `--title 'A b'`, and `NOT_BARE` is unchanged, so #329
did not fix it. At 20:04:45 the main session told Ola "Away mode refused
to open their pull requests, which looks like a fault in the away check
itself" (line 22025): a cause not checked (`CLAUDE.md` §3, *Report only
finished, verified results*). It found the real cause at 20:10 (line
22225), after Ola had left. Even then the rule forbade the redo: the rule in force then (`f058b206`) never redid "a refused push", and #329's rule still would not allow the redo. The single-quote form is stated only in the main session's memory and `docs/increments/h20-away-push-grants.md`; the docstring of `push_grant.unexpandable` also states the single-quote form. The proposal to name the form in the
refusal is proposal 1 of `2026-10-21-merge-lsm38.md`, which is itself unpushed
(finding 3).
2. **#329's harness changes were not in force until 04:59:57 UTC: the main checkout was 50 commits behind.** `/Users/skavhaug/projects/rasputin` is at `f058b206`
(#317, 2026-10-20 15:36); `git rev-list --count HEAD..origin/master`
gives 50. Its last fast-forward was 2026-10-20 16:06 (`git reflog
master`). `.claude/settings.json` runs every hook from
`$CLAUDE_PROJECT_DIR`, that checkout: `grep -c AMEND_FORM
.claude/hooks/guard_push.py` there gives 0, so the amend after `cd` and
the new refusal texts of #329 are inactive, and the main session's
`CLAUDE.md` is the one before #323 and #329. Twelve PRs (#318 to #329)
merged since. Nothing in the recap or `duties.py` says so. The rule
"after merging a change to `CLAUDE.md` or `.claude/agents/`, restart"
(`.claude/REQUIRED-READING.md`) assumes the checkout holds the merge.
Personas read their file from their own worktree (the brief says so),
which is cut from `origin/master`; the hooks and the main session's
instructions are not.
Resolved after this check was written: with Ola's yes the main checkout was fast-forwarded to `2818da34` at 04:59:57 UTC (`git reflog master`), which brought in #318 to #329. Proposal 1 stands, so it does not happen again.
3. **A retrospective dropped from the morning.** The check of private #38
(`worktree-retro-1021e`, `1730e0fd`, handed back 20:37:10) holds three
proposals; `session.md` line 65 has its `ASK OLA`. The morning report
(line 24843) names only the check of #39, and the 04:11 pushes left it
out. It has no review either (`reviews.jsonl`: none on `1730e0fd`). The
recap listed it as a duty on every prompt.
4. **Two branches pushed without a review, in one batch.** Besides #327
(recorded in `2026-10-22-night.md`), private #42 (`36f69487`,
`41100996`): an ideas note, a 123-line Python script and its JSON output,
no `@reviewer` spawn on `lsm-snowcover-2016` and no record in
`reviews.jsonl`. Both `ASK OLA` lines (session.md lines 77 and 79) gave no
review status, while the reviewed branches' lines named the verdict (line
94). Ola's yes at 04:11 covered all four without that difference shown.
5. **A commit after the review on #40.** `1a53c9ce` (one line, the kill
record from `@tester`'s check) came after the recording commit
`4daa2b88`; `docs/increments/README.md` step 4 lets only the recording
commit follow a review. The `ASK OLA` line (94) disclosed it. Small, prose
only; noted for the rule, not as harm.
6. **`session.md` times.** Lines written about 04:45 say "05:xx UTC"; the
transcript stamps them 04:47 to 04:49. One hour off, in a file the next
check reads as a clock.

Checked and in order: #329's record (design rounds 1 and 2, code review
round 1 on `01aa6c17`, APPROVED, in `reviews.jsonl` at 17:36:38); its head
`a4cb795e` is the recording commit only; #328's head `9e25721a` applies the
four wordings of the approved `cf64183d`; #40, #41 and #43 each carry an
approving record on their reviewed head; all four private PRs passed their
one check before the hand merge (`gh pr checks`).

## 3. Is #327's rule clear enough?

Yes. `.claude/REQUIRED-READING.md` says "`@reviewer` runs once on any branch
before its first push, whether or not the branch contains production code",
and `docs/increments/README.md` lists "`@reviewer` before every push" among
what is never dropped. The rule is stated twice and was still missed twice
in one batch (finding 4), so a third statement will not help. What is
missing is a check at the moment of the push: the away grant reads
`reviews.jsonl`, an attended push does not. Proposal 2.

## 4. Lessons, recorded

1. **(This check.) A merged harness change works only once the main
checkout holds it.** Hooks and the main session's `CLAUDE.md` come from
there, not from `origin/master` (finding 2).
2. **(This check.) A push question states the head's review: verdict and
sha, or "not reviewed".** Without it Ola cannot tell the reviewed
branches from the rest (finding 4).
3. **(This check.) Before telling Ola why a guard refused, read the
refusal's own check.** The cause given at 20:04 was a guess (finding 1).

## 5. Proposals

1. **Keep the main checkout current.** `tools/duties.py` gains a duty
"fast-forward the main checkout (N commits behind; hooks or rules
changed)" when `HEAD..origin/master` touches `.claude/`, `CLAUDE.md` or
`tools/`, so it shows in the recap, on every prompt and in Ola's status
line. The main session then runs `git pull --ff-only origin master` in
the main checkout, a ref write that needs Ola's yes. Evidence: finding 2.
Cost: about 12 lines in a governed tool, by day; no rule words.
2. **The push guard shows the review.** When `guard_push.py` asks before an
attended push, pull request or merge, its question adds "no approving
review recorded for <sha>" when `reviews.jsonl` holds none, reusing
`push_grant.review_of`. Evidence: finding 4, and section 3 of the night check (PR #330).
Cost: about 10 lines, governed, by day; no rule words.
3. **A refused pull request may be redone in the form its reason names.**
`NOT_BARE` names the single-quoted `--title`, and the redo sentence of
*Unattended mode* reads "a refused amend or pull request, only in the
form its reason names". Evidence: §1, 29 h 31 min across three PRs.
Supersedes proposal 1 of `2026-10-21-merge-lsm38.md`. Cost: about 4 rule words
and 2 lines in `tools/push_grant.py`.

## 6. Rule text

`python3 tools/rule_sizes.py`: **11,714 words, +51** since `2026-10-22-night.md` (11,663), all from #329: `.claude/REQUIRED-READING.md`
+44, `.claude/agents/architect.md` +10, `CLAUDE.md` −3. Proposals 1 and 2
add none; 3 adds about 4. Cut: `docs/increments/README.md`'s *Never
dropped* list restates "`@reviewer` before every push, Ola's yes for each
push" from `.claude/REQUIRED-READING.md`; replace both items with "the
approval boundary (`.claude/REQUIRED-READING.md`)" (−5 words). No anecdote
added to a rule file by #329.
Loading