@@ -116,6 +116,36 @@ format (Summary / Plan / Test plan), the changelog gate, the push,
116116and the PR-ref follow-up. English throughout, no
117117"Generated with..." lines in the body.
118118
119+ ** A multi-paragraph commit message goes through a file — and that
120+ file belongs to this branch alone.** ` git commit -F ` keeps the prose
121+ out of shell quoting, but the scratchpad is shared by every agent of
122+ one session, so a generic ` commitmsg.txt ` gets overwritten by a
123+ parallel agent and a later re-read commits someone else's text
124+ (sibling repo, 2026-09-05: both a message and a body file were
125+ clobbered mid-run). Take a private directory, which needs no
126+ sanitising at all:
127+
128+ ``` bash
129+ D=$( mktemp -d) # or, if you name it: BRANCH=$(git branch --show-current)
130+ MSG=" $D /commitmsg.txt" # SLUG=${BRANCH//\//-}; MSG="$D/commitmsg-$SLUG.txt"
131+ ```
132+
133+ A branch name is not a filename — ` release/v1.2.3 ` turns the slash
134+ into a directory that does not exist — so substitute the separators
135+ if you derive the name, and never write into a ` $SCRATCH ` you have
136+ not set yourself.
137+
138+ Write the file with the Write tool, then ` git commit -F "$MSG" ` in
139+ the SAME step that wrote it — never re-read one a turn later to reuse
140+ it, because between the two it may belong to another agent. These are
141+ scratch input to one command, not a record; the record is the commit.
142+
143+ The same holds for a PR body you pass as ` --body-file ` . The mandated
144+ ` /pull_request ` command does not take that path — it builds the body
145+ inline with a quoted heredoc (` agentic/commands/pull_request.md `
146+ step 6), which has no collision to avoid — so this applies when you
147+ write a body file yourself.
148+
119149## 3 · After opening: pipeline + review loop (do not skip)
120150
121151Repeat until ** both** hold: all checks pass AND zero unresolved
@@ -135,15 +165,32 @@ On failure: `gh run view --log-failed`, fix, push to the same branch,
135165keep watching.
136166
137167** b. Wait for the Copilot review.** Bot login:
138- ` copilot-pull-request-reviewer[bot] ` . It usually lands within ~ 2 min;
139- if ` gh pr view <num> --json reviewRequests,reviews ` shows neither a
140- request nor a review after the checks pass, request it explicitly
141- (verified working):
168+ ` copilot-pull-request-reviewer[bot] ` . It arrives a few minutes after
169+ the PR is OPENED — not after each push; see "One review per PR"
170+ below. If ` gh pr view <num> --json reviewRequests,reviews ` shows
171+ neither a request nor a review after the checks pass, request it
172+ explicitly (verified working):
142173
143174``` bash
144175gh api -X POST repos/{owner}/{repo}/pulls/< num> /requested_reviewers -f " reviewers[]=copilot-pull-request-reviewer[bot]"
145176```
146177
178+ ** One review per PR is the normal case now.** The ruleset "Automated
179+ Copilot Code Review" (anyplot 10370785, kurrentschrift 18516317)
180+ carries ` review_on_push: false ` since 2026-09-03 — the owner asked
181+ for the churn to stop, and the setting, not any skill, was what
182+ re-reviewed. Two consequences for this loop. A FIX push starts no new
183+ Copilot run, so a ` copilot-* ` check on the new head SHA is
184+ legitimately ABSENT; waiting for one that will never come is the
185+ failure mode to avoid — see §3e for what to require instead. And a
186+ fresh review is requested only after a SUBSTANTIVE rework (new
187+ behaviour, a reworked mechanism), never after every push: each
188+ request re-reads the whole diff and surfaces "previously missed"
189+ findings in files the push never touched, which draws another push
190+ (kurrentschrift #406 collected ~ 15 requests in a day over a one-line
191+ docstring fix). Stop once a round yields no new inline comments but
192+ only carried-over items.
193+
147194Fetch all three comment surfaces — they carry different content:
148195
149196``` bash
@@ -180,6 +227,57 @@ gh api graphql -f query='mutation($id:ID!){resolveReviewThread(input:{threadId:$
180227report the PR URL and final state. ** Do not merge unless explicitly
181228authorized.**
182229
230+ ** Merging on request: wait for the review, not just for green.**
231+ When the owner does ask for the merge in this session, four
232+ conditions, all read on the CURRENT head SHA — re-read it after every
233+ push, ` gh pr view <num> --json headRefOid ` :
234+
235+ 1 . A draft is not reviewable — ` gh pr ready <num> ` first. Copilot
236+ does not review a draft, so a draft merged "green" was never
237+ reviewed, and ` gh pr merge ` on a draft fails anyway. Check
238+ ` isDraft ` .
239+ 2 . Every non-Copilot check on the head SHA is ` completed ` and green.
240+ Dedupe the check runs ** by name, newest wins** : a superseded run
241+ (a label re-trigger, a cancelled first attempt) stays beside the
242+ current one and reads as a red check that is not there any more.
243+ Dedupe on ` .id ` , which grows with creation and is always set — a
244+ check run carries no ` created_at ` , and ` started_at ` stays null
245+ until the run begins, so a ` max_by(.started_at) ` would hand the
246+ row to the OLD completed attempt while the new one is still
247+ queued, which is the failure this step exists to prevent.
248+ ``` bash
249+ gh api repos/{owner}/{repo}/commits/$( gh pr view < num> --json headRefOid --jq .headRefOid) /check-runs \
250+ --jq ' [.check_runs[]] | group_by(.name) | map(max_by(.id)) | .[] | "\(.name): \(.status) \(.conclusion // "")"'
251+ ```
252+ 3 . ** A Copilot review actually exists on the PR** — `gh pr view <num >
253+ --json reviews` , author ` copilot-pull-request-reviewer`. The
254+ head-SHA check run does not prove one: a run reaches ` completed `
255+ with conclusion ` cancelled ` and delivers nothing. So read the
256+ check run only to learn whether a round is still RUNNING
257+ (` queued ` /` in_progress ` means wait) and read the review list to
258+ learn whether one was ever delivered. Since ` review_on_push ` is
259+ off (§3b), the normal state after a fix push is no run on the head
260+ at all with the first round's review standing — that is reviewed,
261+ not unreviewed. If no review exists and the run was cancelled, one
262+ re-request is the whole budget; after that report
263+ green-and-unreviewed and let the owner decide, never loop.
264+ 4 . Zero unresolved review threads (step c), outdated ones included.
265+
266+ ** Merge state is two different fields; read each by its own name.**
267+ ` mergeable ` (` gh pr view --json mergeable ` ) is ` MERGEABLE ` ,
268+ ` CONFLICTING ` or ` UNKNOWN ` — ` UNKNOWN ` right after another merge is
269+ GitHub still computing, so keep polling. ` mergeStateStatus ` is the
270+ richer enum, where the conflicting case is ` DIRTY ` . A conflict is not
271+ transient and has a symptom worth knowing: GitHub starts no CI at
272+ all, so the PR shows no red check, just none (#11212 and
273+ kurrentschrift #524 , 2026-09-04, both read as "checks pending" for a
274+ while). Report it and merge ` origin/main ` into the branch instead of
275+ waiting it out.
276+
277+ Poll all of this from ONE script rather than by hand, and kill a
278+ stale wait loop with the bracket trick (` pkill -f "x[.]y" ` ), or
279+ ` pkill ` matches its own calling shell.
280+
183281## 4 · After merge (when it happens): watch the deploy
184282
185283Merges to ` main ` touching ` api/** ` , ` core/** ` , or ` pyproject.toml `
@@ -206,9 +304,12 @@ a 20-minute poll on the global list never saw the builds). Match the
206304- ** ` isOutdated ` ≠ ` isResolved ` .** A fix-push can outdate a Copilot
207305 thread while it stays unresolved; outdated threads still count
208306 against review-clean — resolve them explicitly.
209- - ** Copilot reviews every push round.** New threads on changed lines
210- are the loop working, not noise — but don't chase cosmetic nits
211- past a couple of rounds; surface stalemates to the user.
307+ - ** A fix push no longer starts a review round.** ` review_on_push ` is
308+ ` false ` since 2026-09-03 (§3b), so only an explicit — and
309+ substantive — re-request opens another one. When a round does run,
310+ new threads on the changed lines are the loop working, not noise —
311+ but don't chase cosmetic nits past a couple of rounds; surface
312+ stalemates to the user.
212313- ** Stacked PRs die when their base squash-merges.** Don't stack; if
213314 work depends on an unmerged PR, wait for its merge (or do the work
214315 and rebase before opening).
0 commit comments