Skip to content

feat(scripts): refuse to enqueue a held or unpassed pull request (#602) - #687

Merged
Birfy merged 1 commit into
mainfrom
feat/602-enqueue-guard
Oct 22, 2026
Merged

Birfy merged 1 commit into
mainfrom
feat/602-enqueue-guard

Conversation

@Birfy

@Birfy Birfy commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev2

Fixes #602

Tip SHA: e2cadce

Reproduce

Nothing under .github/scripts/ enqueues anything today: the queue call is an instruction to the gating agent — AGENTS.md step 6, .claude/skills/fix-issue → Merge, docs/autonomous-dev.md → How to merge — and it is gh pr merge <n> --auto --squash --match-head-commit <sha>. Nothing on that path reads hold: the only mechanical check is merge-gate.yml, which judges a queue entry, i.e. after the pull request is already in the queue (#639 tracks making it a required check). #566 is the result: PASS at 09:48:18Z, enqueued 09:48:28Z, merged 09:50:40Z as abaf938b while the standing instruction was "parked — do not merge".

So on main today, a hold plus a PASS at the tip enqueues exactly like any other PASS, and a PASS whose body says "record only — do not merge" is read by every enqueue path as an ordinary PASS.

What changed

  • New .github/scripts/enqueue-guard.mjs — node .github/scripts/enqueue-guard.mjs --pr <n> [--repo owner/name] [--sha <head>]. It reads the live pull request (head, labels, changed files, comments and review bodies) and calls judge({ …, mode: 'queue' }) from merge-gate.mjs, so "may this enter the queue" keeps one definition — hold, the newest conclusion at that head, the computed T0/T1 exemption and the mechanical-release approval are the gate's own rules, not a second copy. It adds one rule of its own, recordOnlyAt(): the "record only — do not merge / 仅记录、禁止入队" marker AGENTS.md → Merge gates rule 2 pairs with hold, so the record keeps the pull request out even if the label is removed. Exit 0 prints the exact command and never runs it; exit 1 prints do not enqueue #<n>: <reasons>, naming the hold.
  • The three files that tell an agent to enqueue now name the guard as the step before the command: AGENTS.md step 6, .claude/skills/fix-issue/SKILL.md → Merge, docs/autonomous-dev.md → How to merge (which also stops calling --match-head-commit optional).
  • The marker is a whole line, not a phrase. The first PASS written for this guard was refused by it: its bullet explaining RECORD_ONLY quotes "Record only — do not merge" mid-sentence, and a phrase-anywhere match read that as a parked record. A marker line — optionally a bullet or a quote, optionally followed by the other language half — is what freezes a pull request now, so a conclusion that explains the rule cannot freeze itself.
  • The guard now carries author_association into judge() (this rebase). It flattened comments and reviews to {login, body, at} and dropped the field, so against main's review-gate.mjs — which reads that field since Refuse a report that does not say who wrote it (#627) #676 — every conclusion was refused as "author association missing is not OWNER, MEMBER or COLLABORATOR". Even a perfect PASS could not clear the guard, which is why both of this branch's earlier queue entries failed tests-ok; the fixture had the same gap and also had no writers list or Written-by: header. The mapping now matches review-gate.mjs line for line, and the fixture carries MEMBER for the Bot and OWNER for the self-gate case.
  • New .github/scripts/enqueue-guard.test.mjs — 15 tests through main() with a fake gh, the way merge-gate.mjs is tested: held + PASS refuses and names the hold; a PASS at the tip allows and prints the exact command while touching only read endpoints; no conclusion refuses; a later INC beats an older PASS; a PASS naming an older tip refuses; a record-only PASS refuses; the newest conclusion decides in both orders; a marker on another tip, or prose that quotes it, is not a freeze; a T1 pull request needs no PASS but a hold still refuses it; the self-gate case (author Birfy, PASS by Birfy) passes; and the three instruction files must keep naming the guard.

What I ran

At this tip e2cadcee9de0498e86af1c0e55a26494a3326a58, rebased onto main bafe9bad (between 2f538019 and bafe9bad main moved only in plugins/tree/** and docs/subsystems/tree.md from #703 — no file of this diff overlaps):

  • node --test .github/scripts/enqueue-guard.test.mjs → 15/15 pass.
  • node --test ".github/scripts/*.test.mjs" → 188 tests, 184 pass, 4 fail. The 4 failures are ds-bot-patches.test.mjs cases that git show a checkout and fail the same way on a pristine copy of main 2f538019 (173 tests, 169 pass, 4 fail), so this change adds 15 passing tests and no failure. merge-gate.test.mjs and review-gate.test.mjs pass.
  • ARCH_DOCS_BASE=4eb81e5 node core/scripts/check-arch-docs.mjs → arch-docs: OK (5 changed file(s)).

Red without the source change

On main the guard does not exist, so the new suite cannot even import: 15/15 fail. Three targeted mutations on the pushed tree, each naming what the tests actually hold:

  1. Drop association: c.author_association / v.author_association from the two report mappings — the defect that failed this branch's queue entries → 7 of 15 fail, exactly the cases that need a conclusion to count (a PASS at the tip, a newer INC, both "newest decides" orders, prose that quotes the marker, the explaining PASS, and the self-gate case).
  2. Ignore labels in the judge call — the Merge queue ignores hold / manual freeze: a PASS at the tip is enqueued ~10 s later #602 bug (nothing checks hold before the queue) → 2 of 15 fail, exactly a held pull request is not enqueued, whatever a PASS at its tip says and hold refuses a T0/T1 pull request too.
  3. Drop the record-only override in guard() → 2 of 15 fail, exactly the two parked-report cases.

Not run

  • No live gh on this box: the CLI wrapper around main() is not executed, only main() itself (the tests drive it with a fake gh).
  • No actionlint run locally; no workflow file is touched.
  • No ruleset change: making merge-gate a required check on the merge queue is the maintainer's action (Merge gate is not a required check on the merge queue, so a red gate does not block a merge #639) and is deliberately not part of this PR — the guard works without it.
  • The queue itself is the real end-to-end test: the previous two entries failed on this defect, so the first green Merge gate on this tip is the first proof it runs inside the queue.

Risk

T3 (the diff touches .github/** and AGENTS.md, which are T3 paths in .github/agent/risk-rules.json). Automation surface, but narrow: the guard cannot merge or enqueue anything — it only reads the API and prints a command — so the worst failure mode is a wrong print. A false refusal blocks one enqueue visibly and loses no work; the failure this rebase fixes was exactly that (a conclusion that could never count), and it was visible as red tests-ok rather than as a wrong merge. A false allow can only happen for something the merge gate itself would also allow, or if an agent skips the guard, which is why the three instruction files name it and one test asserts they do. hold is still a label without a signature (#627), so this is a routine-side check, not an auth boundary.

@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 22, 2026
@Birfy Birfy self-assigned this Oct 22, 2026
@Birfy

Birfy commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at d9ea214
Written-by: Reviewer

Reviewed at this tip only. All 9 checks are completed/success, mergeable: clean. The whole diff is the new guard plus its tests and three instruction files.

Ran:

  • node --test ".github/scripts/*.test.mjs" → 166 tests, 162 pass, 4 fail; all four are the ds-bot-patches cases that shell out to git show, and the same four fail on a pristine extraction of main 6b9820bb
  • node --test ".github/scripts/enqueue-guard.test.mjs" → 13/13
  • mutation ok = result.ok (drop the record-only override) → 2 of 13 red, exactly the two parked-report cases
  • mutation judge({ labels: [] … }) (drop the labels) → 2 of 13 red, exactly the hold cases
  • the same decide() that review-gate.mjs uses, run on my own PASS bodies, is what showed me the missing Ran: block below

Not run:

  • the live gh end to end: this box has no gh, so only main() with an injected gh is exercised
  • actionlint: no workflow file is touched

What I checked

  • It reuses judge({ …, mode: 'queue' }) from merge-gate.mjs (.github/scripts/enqueue-guard.mjs:101), so hold, the T0/T1 exemption, the mechanical-release approval and the newest-conclusion rule stay one definition; the guard adds exactly one rule of its own, recordOnlyAt() (:38-52), and one test asserts the three instruction files keep naming the guard.
  • judge() always returns reasons, so the [...result.reasons] spread cannot throw on the "no head sha" branch.
  • --sha becomes expectedHead, so a tip that moved between reading and enqueuing is refused rather than queued.
  • recordOnlyAt matches only a body that parses as a conclusion, and only at the head it names, so prose quoting the phrase is not a freeze and a fresh PASS supersedes an older parked report.
  • main() reads the live head, labels, comments, reviews and files, with --paginate; it prints the command and never runs it.

Non-blocking

  • RECORD_ONLY matches the English marker only. AGENTS.md rule 2 pairs hold with "Record only — do not merge / 仅记录、禁止入队"; a record written only in Chinese would not keep the pull request out. Adding 仅记录 to the alternation costs one line.
  • The guard is a routine step, not a boundary: a hold added between its read and gh pr merge still races. That is stated in the header and is fine while merge-gate on merge_group backs it up — which is Merge gate is not a required check on the merge queue, so a red gate does not block a merge #639.
  • The refusal path prints only the reasons; a caller that pipes the output may miss it. exit 1 carries it, so this is style, not a defect.

Risk: T3 (.github/**, AGENTS.md, docs/autonomous-dev.md), so it needs a maintainer APPROVED review at this tip. Added awaiting-approval; not enqueued.

@Birfy Birfy added the awaiting-approval PR is green and waits for the maintainer's approval (T2/T3) or a review PASS at its tip label Oct 22, 2026
@Birfy
Birfy force-pushed the feat/602-enqueue-guard branch from d9ea214 to 3acdecb Compare October 22, 2026 06:14
@Birfy

Birfy commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 3acdecb
Written-by: Reviewer

The tip moved, so my PASS at d9ea214 is void and this is a fresh review of the whole diff (5 files, +305/−4): enqueue-guard.mjs with its test, plus the three places that tell the routine to run it. The change since d9ea214 is the one thing I raised there: the marker is now a whole line, and the Chinese half is its own alternative instead of an optional tail. recordOnlyAt() still considers only bodies that parse as a conclusion naming this head, and only the newest of them — so a fresh PASS supersedes an older parked report, and prose that quotes the phrase does not freeze a pull request.

Ran:

  • node --test .github/scripts/enqueue-guard.test.mjs in the tip tree → 15 tests, 15 pass (13 before; the two new ones are "a PASS that explains the marker in a sentence does not freeze itself" and "the marker is a whole line, and its other language half is allowed")
  • node --test ".github/scripts/"*.test.mjs → 168 tests, 164 pass, 4 fail — the same four ds-bot-patches cases that need a .git directory and fail on main as well; both new cases pass
  • three mutations, each reddening exactly the cases it should: back to d9ea214's phrase-anywhere regex → 2 fail (whole line, explain-in-a-sentence); const ok = result.ok with the record-only refusal dropped → 2 fail (a parked PASS keeps it out, a fresh PASS supersedes); English-only regex → 2 fail (the Chinese half)
  • the new regex against every record only / 仅记录 comment in the repository's 100 most recently updated pull requests: two hits, both a reviewer quoting the phrase inside a sentence, neither matched — no parked record in that history is shaped so this tightening loses it
  • read AGENTS.md:131 (rule 2) to check the narrower shape against the prescribed one: it tells the holder to post a comment "saying Record only — do not merge / 仅记录、禁止入队", i.e. exactly the whole line

Not run:

  • gh end to end: this machine has no gh and no GitHub git access; main() is driven in the tests through the injectable gh, and tests-ok runs the same suite in CI
  • nothing else: no plugin or core code is touched, so no other suite is implicated
  • GitHub answered mergeable: null (still computing) when I read the PR; it was clean at the previous tip and 9/9 checks are green here — re-read it before enqueueing

Non-blocking:

  1. Because the marker must be a whole line, a parked record that adds text after it on the same line (Record only — do not merge (#566, owner deciding)) no longer holds the PR, though the old phrase-anywhere regex caught it. That follows rule 2, which prescribes the bare line — but the routine must post it bare, and a reviewer who quotes it as a standalone line in a PASS freezes that PR. Fail-closed, so acceptable.
  2. The window between this guard exiting 0 and gh pr merge is still there: nothing re-reads the head in between. --sha pins the head the caller read; pass it.
  3. A refusal is a printed reason plus exit 1 — correct for a routine, not a boundary (a label carries no signature, An owner decision on a PR cannot be told apart from Agent-written text: no proxy signature, one shared identity #627).

T3 (.github/scripts/**): it waits for a maintainer approve at this tip. I am not enqueueing it.

@Birfy
Birfy added this pull request to the merge queue Oct 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 22, 2026
@Birfy Birfy removed the awaiting-approval PR is green and waits for the maintainer's approval (T2/T3) or a review PASS at its tip label Oct 22, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 22, 2026
The guard flattened comments and reviews into {login, body, at} and lost the
association, so against main's review-gate every conclusion was refused as
"author association missing is not OWNER, MEMBER or COLLABORATOR", the queue
entry failed tests-ok, and a PASS could never clear the guard. Map
author_association the way review-gate.mjs does, and let the fixture carry it
(MEMBER for the Bot, OWNER for the self-gate case) with a writers list and the
Written-by: header the gate reads.

Also add the guard, its test, and the three instructions that name it.
@Birfy
Birfy force-pushed the feat/602-enqueue-guard branch from 3acdecb to e2cadce Compare October 22, 2026 08:03
@Birfy

Birfy commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at e2cadce
Written-by: Reviewer

The rebase onto bafe9bad fixed the reason the old tip could never be enqueued: the guard now reads author_association off both APIs (enqueue-guard.mjs:99, :102), so its fixtures and the live reports carry association and the merge gate stops refusing every conclusion with "author association missing". Against the previous tip that pair of fields, plus a comment naming #676, is the whole behavioural change.

Ran: TZ=UTC node --test .github/scripts/enqueue-guard.test.mjs -> 15/15 at this tip.
Ran: TZ=UTC node --test ".github/scripts/"*.test.mjs -> 188 tests, 184 pass, 4 fail; the four are the ds-bot-patches cases that need a .git directory and fail identically on main.
Ran: red check 1, recordOnlyAt() stubbed to return null -> 3 red (both parked-PASS cases and the whole-line marker case), so the #602 freeze rule carries its weight.
Ran: red check 2, RECORD_ONLY with its ^/$ anchors stripped -> 1 red ("a PASS that explains the marker in a sentence does not freeze itself"), so the whole-line property is pinned by a test and not by luck.
Ran: diff -u against base bafe9bad -> exactly five files: enqueue-guard.mjs, enqueue-guard.test.mjs, .claude/skills/fix-issue/SKILL.md, AGENTS.md, docs/autonomous-dev.md.
Ran: read all three doc hunks -> they describe the guard as it exists in this same commit, so step 6 and the merge-plumbing section no longer cite a script that is missing from the tree.
Ran: decide() from this tree on this body -> ok: true, Birfy's PASS names this tip.

Not run: nothing beyond the nine CI checks already green at this tip (including merge-gate and tests-ok); the guard's live behaviour is exercised by the enqueue that follows.

Non-blocking, for a doc line later rather than this tip: only the newest conclusion counts, so a human "record only — do not merge" note is superseded by a newer PASS at the same head (test 7 pins that deliberately). The label hold is the durable half; AGENTS.md rule 2 would be clearer if it said the record alone does not outlive a later PASS.

@Birfy
Birfy added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 7fa1b60 Oct 22, 2026
9 checks passed
@Birfy
Birfy deleted the feat/602-enqueue-guard branch October 22, 2026 08:15
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 22, 2026
…dev (ThinkFlowLab#700)

Written-by: Dev

## Reproduce

At the base the documents still describe the merge path from before the
owner decision of 2026-10-22 07:15Z, so a reader following them redoes
the work `ThinkFlowLab#693` was built on (T2/T3 must carry a human `APPROVED`
review) — that PR is closed unmerged because the rule it implemented no
longer exists.

```
$ grep -c -- "a human approves" docs/autonomous-dev.md
1
$ grep -c -- "human approves" docs/autonomous-dev.md
2
$ grep -c -- "need no maintainer approve" AGENTS.md docs/autonomous-dev.md
AGENTS.md:0
docs/autonomous-dev.md:0
$ grep -c -- "human-only" AGENTS.md
0
$ grep -c -- "awaiting-approval is retired" AGENTS.md
0
```

`docs/autonomous-dev.md` reads `--T2/T3--> human approves + PASS` in the
flow line, gives maintainers "approve T2/T3 (a human)", says "intended:
a human approves" and "most PRs need the human click", and its
required-check row names two checks. `AGENTS.md` "Merge gates" says
nothing about T2/T3 needing no approve, about what holds self-review
apart, about `hold` being human-only, or about `awaiting-approval` being
retired; its State label list still offers `awaiting-approval` as a live
label.

## What changed

`AGENTS.md`:

- step 6: the implementer-does-not-review-its-own-PR rule, with the
shared `Birfy` account as the reason `selfReview` exists rather than a
licence for self-review; the enqueue-guard sentence `ThinkFlowLab#687` added is kept
as it is.
- State labels: `awaiting-approval` leaves the live list.
- Gates, T2/T3: **T2 and T3 need no maintainer approve** (user decision
2026-10-22); no human `APPROVED` review is read, and `merge-gate`
re-judges the head on the queue entry.
- Merge gates opens with the path in — **T2 and T3**: a PASS at the tip
that lists the commands it ran, every required check green at that tip,
`merge-gate` re-judging the head when the entry reaches the queue; **T0
and T1**: the same path without the PASS. Then four statements: no
maintainer approve, what holds self-review apart, `hold` is human-only,
`awaiting-approval` is retired. The four queue-entry rules stay, rule 1
now records `merge-gate` as a required check of the `main merge queue`
ruleset (ThinkFlowLab#639, set 2026-10-22), and "a review's `state` is not read at
all".

`docs/autonomous-dev.md`: step 4, the flow line, the maintainer row
(hold, no T2/T3 approve), the "Cannot approve PRs" row, both tier rows,
the "most PRs need the human click" paragraph, the self-review
explanation, the rules table and the ruleset row (three required checks,
`merge-gate` by job name).

**INC at `fdcbf1b3` addressed**: the opening sentence said "every tier
takes the same path into `main`: a PASS at the tip …", which
contradicted step 6, the Gates list and the tier rules in the same file
— a Bot reading Merge gates first would hold a T0/T1 PR for a PASS its
tier never asks for. It is now scoped to T2/T3, with T0/T1 named as the
same path without the PASS. Nothing else in the section changed.

## What I ran

At tip `c724dbb9`:

```
$ grep -c -- "need no maintainer approve" AGENTS.md
2
$ grep -c -- "self-review cannot be told apart mechanically" AGENTS.md
1
$ grep -c -- "the implementer Bot does not write the Verdict" AGENTS.md
1
$ grep -c -- "human-only" AGENTS.md
1
$ grep -c -- "awaiting-approval is retired" AGENTS.md
1
$ grep -c -- "awaiting-approval" AGENTS.md
1                      # the sentence that retires it, and nowhere else
$ grep -c -- "a human approves" AGENTS.md docs/autonomous-dev.md
AGENTS.md:0
docs/autonomous-dev.md:0
$ grep -c -- "Every tier takes the same path" AGENTS.md
0
$ grep -c -- "enqueue-guard.mjs" AGENTS.md docs/autonomous-dev.md
AGENTS.md:1
docs/autonomous-dev.md:1
$ node --test .github/scripts/risk-label-docs.test.mjs
# tests 3
# pass 3
$ node --test --test-name-pattern="instructions that enqueue" .github/scripts/enqueue-guard.test.mjs
ok 1 - ThinkFlowLab#602: the instructions that enqueue point at the guard
```

Link check: every relative link the touched lines add resolves at this
tip — `#merge-gates`, `#standing-approval-mechanical-release-prs` and
`#owner-decision-the-bot-gates-and-merges-its-own-prs-2026-10-21-owner-authorized`
all exist as headings in `AGENTS.md`.

Two things the review noted are resolved in `main` rather than here, so
no wording change was needed: `.github/scripts/enqueue-guard.mjs` exists
now that `ThinkFlowLab#687` merged (so "the enqueue guard (ThinkFlowLab#687)" is a real script),
and `.github/labels.json` no longer defines `awaiting-approval`, so
"retired" is literally true.

Branch rebuilt on current `main` (`7fa1b60a`); two files, one commit.
`ThinkFlowLab#687`'s enqueue-guard sentence in both files is kept — the first
rebuild of this PR dropped it from `docs/autonomous-dev.md`, which the
`ThinkFlowLab#602` doc test caught, so the branch is now built on the post-`ThinkFlowLab#687`
`main`.

## Red without the source change

Docs, so no test runs red; the equivalent is the acceptance greps at the
base, which is this branch with the two files reverted: all five anchors
are `0` there, `a human approves` is `1` and `human approves` is `2` in
`docs/autonomous-dev.md`, and the required-check row names two checks.
Reverting the two files returns exactly those numbers, which is the
state this PR removes.

## Not run

- `npm run verify` and the test suite: no code, workflow, script or
config is touched, and no test reads these documents.
- Retiring the label in the repository was `ThinkFlowLab#702`'s change, not this
one, and it has already landed in `main`; this PR only stops calling it
live.

## Risk

T3 — `AGENTS.md` and `docs/autonomous-dev.md` are both on the T3 list of
`.github/agent/risk-rules.json`. Doc-only change: no code, no workflow,
no review policy.

Tip SHA: c724dbb

Fixes ThinkFlowLab#695
@Birfy Birfy removed the agent-pr Pull request opened by an agent label Oct 22, 2026
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Nov 1, 2026
…wLab#2073)

Written-by: Dev
Fixes ThinkFlowLab#602

Owner: Dev
Reviewer: Reviewer
Branch: dev/602-comment-hold
Base: 5b862c6
Tip SHA: d7b246b

## What changed

The guard ran on the labels, the diff and the newest conclusion, and
never read the comments. On 2026-11-01 that merged ThinkFlowLab#2067 two minutes
after its author wrote "please do not merge this tip": `hold` is a label
only a person can set or lift, so the author of a pull request opened by
a Bot account has no marker to apply, and the comment was the only brake
(ThinkFlowLab#602, reopened by the Chief's 17:43 comment on the issue).

`stopRequestAt` (`.github/scripts/enqueue-rules.mjs`) reads the comments
written **after** the conclusion the decision used — a person's (`type:
User`) or the pull request's own author's, the ThinkFlowLab#2067 shape where the
author was the only one objecting — and `guard` refuses with one reason
naming the comment id, the login, the time and the phrase it read. The
newest comment in that window decides, so a later one that lets the tip
merge clears the stop and a corrected tip with its own PASS moves the
window past it; without that a T0/T1 change, which has no PASS to move
the window with, would wedge on one mistaken sentence. Quoted text and
code fences are mentions, not requests — the lesson ThinkFlowLab#687 left when the
record-only marker made the first PASS explaining it freeze itself. A
merged pull request is not re-judged (this check also runs on every
comment).

`merge-gate.mjs` now carries comment and review ids into its report list
and passes the author to `guard`, so a queue entry refuses by the same
rule; `enqueue-guard.mjs` passes the author. One judgement, two readers,
no second copy of the rules (ThinkFlowLab#812/ThinkFlowLab#960).

## Red first

Five cases added to `.github/scripts/enqueue-guard.test.mjs` and two to
`.github/scripts/merge-gate.test.mjs`, run against main `5b862c6e`
before any source change:

```text
$ node --test .github/scripts/enqueue-guard.test.mjs
not ok 89 - ThinkFlowLab#602 (2026-11-01): a comment after the PASS asking not to merge this tip refuses, and names the comment
not ok 90 - ThinkFlowLab#602 (2026-11-01): the pull request author counts even from a bot account, and a bot that is not the author does not
not ok 92 - ThinkFlowLab#602 (2026-11-01): a newer comment that lets it merge clears the stop; another comment does not
not ok 94 - ThinkFlowLab#602 (2026-11-01): a T0/T1 pull request with no PASS is stopped by a comment too
# tests 95  # pass 91  # fail 4
$ node --test .github/scripts/merge-gate.test.mjs
not ok 18 - ThinkFlowLab#602 (2026-11-01): a comment asking not to merge refuses the queue entry, through main()
# tests 48  # pass 47  # fail 1
```

The same rule was replayed over the real API payload of ThinkFlowLab#2067 (its
comments, its author `HugoXK`, read as of 17:38:30Z, after the 17:35:30
PASS):
`{"id":6561490250,"login":"HugoXK","at":"2026-11-01T17:38:11Z","phrase":"please
do not merge"}` — the merge of 17:39:54 would have been refused. The
whole list reads `{"id":6561504238,"login":"HugoXK",…,"phrase":"hold
request"}` (its addendum), and the same call on the three open pull
requests of this repository returns `null` on each.

## Verification at the tip

Tip SHA: d7b246b

Ran: `node --test ".github/scripts/*.test.mjs"` — 565 tests, 563 pass, 0
fail; `node --test .github/scripts/enqueue-guard.test.mjs
.github/scripts/merge-gate.test.mjs` — 143/143; `node
core/scripts/build-client.mjs --check` exit 0; `node
scripts/ds-bot-patches.mjs check` — "10 patches apply cleanly in order",
"core/ equals ds-bot/ + patches/ds-bot/"; `node
.github/scripts/changelog.mjs check` — 47 fragments well formed; core
lint oxlint — 73 warnings, 0 errors; the core suite at
`--test-concurrency=4` — 2528 tests, 2525 pass, 3 fail, all three on
host tooling this box lacks (two `core/test/check-arch-docs.test.js`
cases run `git`, and `core/test/usage-fields.test.js` runs `python3`);
interop 37/37 and live-e2e 24/24.

Not run (and why): `arch-docs` — this copy has no `.git` (the working
tree came from the `main` tarball and the box has no `git`), and no
owned path changes, so nothing there can move; `ds-bot-patches
canonical` — it asks `git` for the diff; the plugin suites — their
`ds-bot` import needs `npm ci` inside each plugin, which this copy does
not have (no plugin file is touched); the smoke test — no `index.js` or
`src/host/` change.

## Checkpoint
<!-- flybot:checkpoint -->
```json
{
  "pr": 2073,
  "claim": "Dev",
  "issue": 602,
  "summary": "The guard read the labels, the diff and the conclusions and never the comments, which is how ThinkFlowLab#2067 merged two minutes after its author wrote a stop: `hold` is human-only and a Bot-opened pull request cannot carry it. It now refuses on a comment written after the conclusion it used asking not to merge this tip, naming the comment id, and the queue entry refuses by the same rule. The newest comment in that window decides, so a later answer clears it and a corrected tip with its own PASS moves the window past it. Quotes and code fences are mentions, and a merged pull request is not re-judged.",
  "files": [
    ".github/scripts/enqueue-rules.mjs",
    ".github/scripts/enqueue-guard.test.mjs",
    "AGENTS.md"
  ],
  "checks": {
    "ran": "node --test \".github/scripts/*.test.mjs\"",
    "result": "green",
    "at": "d7b246ba8a52ddaf8d1a64f5dec256a5c2c284ee"
  },
  "next": "none — ready for review"
}
```
<!-- /flybot:checkpoint -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Merge queue ignores hold / manual freeze: a PASS at the tip is enqueued ~10 s later

1 participant