Skip to content

Commit 00f34d5

Browse files
committed
docs(agents): 合并指引收口为一条实测的队列教条 —— 两处 --auto 禁令与队列 hedge 不再自相矛盾 (#6118)
AGENTS.md 三处合并指引互相矛盾:§7 标题「Never `gh pr merge --auto`」、 Post-Task Checklist 第 2 条重复同一禁令,而 §7 自己下面又写着「队列启用后 add to queue 才是正道」—— 挂 auto-merge 正是入队手段,今天的落地实践走的 也正是 auto-merge → 入队 → rebuild-then-land。 按本仓实测重写(⛔ 不照抄 objectui#3522 的叙述,也不外推它的 405): - §7 改为「在已绿、已验收、非 draft 的 PR 上挂 auto-merge,由队列合并」, 并写明本仓「队列会校验」的具体所指:22 个 workflow 中 3 个带 `on: merge_group:`(ci.yml / lint.yml / spec-liveness-check.yml), Actions API 报 2742 次 merge_group 运行,最近 30 次全在 gh-readonly-queue/main/pr-* 世代上且全绿;merge-queue-triage.yml 不是 订阅者而是 workflow_run 观察者。 - 保留旧禁令为真的那一半:⛔ 不在没绿/没验收的 PR 上挂 —— 红着挂不会入队 只会藏起来(#4852 空转 100 分钟)。 - 精确写出队列的边界:只有 required 集里的检查有拦截力。#6067 的队列世代 Lint & Type Check 已 completed/failure(run 31136745851)仍于 01:13Z 合入, 其红随 merge ref 毒化后续每个 PR 的 ESLint job(#6100,同形于 #5584#5601#5615);#5617 下维护者已把 ESLint 与 TypeScript Type Check 加入 分支保护 required 集与队列检查集。 - 三条今晚实测的重挂情形:队列踢出会掉 auto-merge(#6059,01:03:02Z 踢出 → 01:04:15Z 重挂 → 01:25:00Z 落地)、前车失败导致的连带踢出按设计无评论 (merge-queue-triage.yml 只在 conclusion == failure 时发言)、转回 draft 同时掉 auto-merge 与队列成员资格(仓内操作纪律,非本轮 API 实测,已标注)。 - 未测项如实标注:直接 `gh pr merge`(非 auto)在本仓是否 405 —— 需在活 PR 上真做一次合并才能确证,不值得做;objectui 的 405 不外推。 - Post-Task Checklist 第 2 条改指同一条路径;§10 里「(and the merge queue, once enabled)」这个同族过期 hedge 一并改正。 编号完整性:§1–§11 一个都没动位。反向引用核过 —— pm-dispatch/SKILL.md:1544 引 §7 的「无队列时串行合并」仍成立(fallback 段原样保留),:1590 引 §3、 :1736 引 §10、AGENTS.md:83 引 §8、os-dev.md:114 引 §9 均未受影响。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
1 parent 01faeb1 commit 00f34d5

1 file changed

Lines changed: 88 additions & 20 deletions

File tree

AGENTS.md

Lines changed: 88 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -190,20 +190,87 @@ Even inside your own worktree, operate defensively:
190190
your working-tree change between the edit and the commit. On a real conflict,
191191
re-apply only *your* lines and let the PR merge integrate the rest.
192192
6. **Don't rebase or force-update shared branches** to tidy other agents' commits.
193-
7. **Merge only after remote CI is fully green. Never `gh pr merge --auto`.**
194-
Auto-merge can land a still-red PR onto shared `main` and break it for every
195-
parallel agent (see #1475). Merge serially; rebase other open branches before
196-
merging the next one.
197-
**Once the repo's merge queue is enabled, "add to queue" IS the sanctioned
198-
path** — it is the opposite of the auto-merge this rule bans: the queue
199-
builds your PR *as merged onto the current `main`* and lands it only if that
200-
speculative result is green, which is exactly the §10 re-verification, done
201-
by the platform, race-free. The manual serial protocol above is the fallback
202-
for when the queue is unavailable. (Why this matters: `main` can land a PR
203-
every few minutes at peak; a manual merge–reverify loop takes ~25 minutes,
204-
so under load it *never* wins the race — one PR went three full green
205-
cycles without managing to land. That is a livelock, not a discipline
206-
failure.)
193+
7. **Land through the merge queue: arm auto-merge on a PR that is already
194+
green, accepted and non-draft, then let the queue merge it.** Arming is how
195+
you enter the queue, and the queue is what makes arming safe — it rebuilds
196+
your PR *as merged onto the current `main`*, re-runs the subscribing
197+
workflows on that rebuilt generation, and lands it only if the required ones
198+
pass. That is the §10 re-verification, done by the platform, race-free.
199+
200+
**What "the queue validates" means here, measured** (`origin/main`,
201+
2026-08-07): three of this repo's 22 workflows carry an `on: merge_group:`
202+
trigger — `ci.yml`, `lint.yml`, `spec-liveness-check.yml` — and the Actions
203+
API reports **2742** `merge_group` runs, the most recent 30 all on
204+
`gh-readonly-queue/main/pr-<n>-<sha>` refs, all three workflows, all green.
205+
A fourth workflow, `merge-queue-triage.yml`, is *not* a subscriber: it
206+
watches those runs through `workflow_run` and comments the diagnosis on the
207+
PR when a queue build goes red (#4859).
208+
209+
**This supersedes the older "never `gh pr merge --auto`" ban.** Its premise —
210+
auto-merge lands a still-red PR on shared `main` (#1475) — is inverted by
211+
rebuild-then-land, and the ban forbade what is now the sanctioned path. Its
212+
*true half* survives, as a precondition rather than a prohibition: **arm only
213+
what is already green and accepted**, where green means the gate-carrying
214+
jobs' `conclusion` is `success`, not "no failure yet" — `in_progress` is not
215+
a pass. Arming a red PR does not queue it, it hides it: #4852 sat armed from
216+
10:15 for **100 minutes** without ever entering the queue, every poll
217+
misreading "not on `main` yet" as "queued". So always read *two* things when
218+
checking on a landing: the queue branch **and** `origin/main`.
219+
220+
**The queue enforces only the required set; everything else is advisory and
221+
rides through.** #6067's final queue generation
222+
(`gh-readonly-queue/main/pr-6067-db0d53c2…`) had `Lint & Type Check` at
223+
`completed/failure` — run 31136745851, concluded 01:12:11Z — and merged at
224+
01:13Z regardless; the `check:slot-lookup` red it carried then rode `main`'s
225+
merge ref into every following PR's ESLint job until it was stanched (#6100,
226+
the same shape as #5584#5601 → hot-fix #5615). Governance half: **#5617**,
227+
under which the maintainer on 2026-08-07 added **ESLint** and **TypeScript
228+
Type Check** to both `main`'s required-status-check set and the queue's check
229+
set, so those two now block — the audit archived on that issue also lists
230+
which other jobs can and cannot safely join them. A gate outside that set
231+
stops nothing, which is why "arm only on green" is a rule and not a
232+
formality.
233+
234+
**Three re-arm situations this repo has actually hit.** None of them is a
235+
reason to avoid the queue; all are reasons to confirm a PR is still *in* it:
236+
- **A red queue build ejects your entry and drops the auto-merge.** The
237+
failure is often in a package your PR never touched, because the queue runs
238+
the *full* suite while the PR ran affected-only. #6059 was ejected at
239+
01:03:02Z on a known flaky (`datasource-pool-support.test.ts`, #6044),
240+
diagnosed against the triage comment, re-armed at 01:04:15Z and landed at
241+
01:25:00Z. Recognise the signature first, then re-arm once — never re-queue
242+
reflexively.
243+
- **Collateral eviction is silent by design.** `merge-queue-triage.yml`
244+
comments only on `conclusion == failure`; an entry cancelled because
245+
something *ahead* of it failed gets nothing, since that outcome says
246+
nothing about your PR. A PR that is neither on `main` nor in the queue was
247+
dropped — re-arm it.
248+
- **Flipping back to draft drops auto-merge and queue membership at once, and
249+
neither returns by itself.** The order is therefore fixed: ready *first*,
250+
arm *second*. (This one is the repo's standing operating note —
251+
`.claude/skills/pm-dispatch/SKILL.md` note 1 — not an API measurement.)
252+
253+
And one non-fix: **a stale red does not clear by re-running.**
254+
`rerun_failed_jobs` reuses the original run's commit and merge ref, so it
255+
cannot see a fix that landed on `main` since. Compare the fix's merge time
256+
against the run's creation time; if the fix is later, only a new commit
257+
(`git merge origin/main`) helps — #4852's red was byte-identical across a
258+
rerun until #4856 landed.
259+
260+
**Not measured here:** whether a direct, non-auto `gh pr merge` is refused
261+
with `405 Changes must be made through the merge queue`. Establishing that
262+
would mean actually attempting a merge on a live PR, which is not an
263+
experiment worth running. objectui returns 405; that is **not** extrapolated
264+
to this repo (objectui#3243) — separate rulesets, and #5617's cross-repo
265+
audit found the two configured differently.
266+
267+
**Fallback, when the queue is unavailable:** the old manual protocol — merge
268+
serially, only after remote CI is fully green, rebasing other open branches
269+
before merging the next one. It is a fallback because it loses under load:
270+
`main` can land a PR every few minutes at peak while a manual merge–reverify
271+
loop takes ~25 minutes, so one PR went three full green cycles without
272+
managing to land. That is a livelock, not a discipline failure — and it is
273+
why the queue is the default path rather than an optimisation.
207274
8. **Testing needs a server? Start your own temporary one — never stop someone
208275
else's.** A running dev server you didn't start probably belongs to another
209276
agent or the user; killing it (or its port) breaks their in-flight work. Spin
@@ -253,8 +320,9 @@ Even inside your own worktree, operate defensively:
253320
- **Full `pnpm typecheck && pnpm test` again only when** the incoming
254321
commits touch the same packages or the same behavior your diff does, or a
255322
conflict occurred outside trivially-mechanical files.
256-
- CI on the PR (and the merge queue, once enabled) validates the merge
257-
commit itself — that second CI round is where joint breakage surfaces, and
323+
- CI on the PR, and then the merge queue on its rebuilt generation (§7),
324+
validates the merge commit itself — that second CI round is where joint
325+
breakage surfaces, and
258326
the guards in `scripts/check-*.mjs` exist largely because this class of
259327
breakage is invisible to `git merge`.
260328
11. **Generated artifacts don't text-merge — a driver defers them and
@@ -765,10 +833,10 @@ it to `OPEN_CAPABILITY_REGISTRIES` in the same PR that fixes it.
765833

766834
1. `pnpm test` — verify nothing broke. Touched a type-check-covered package? `pnpm typecheck` too.
767835
2. **Land it — don't leave passing work in the working tree.** Once tests pass,
768-
create a feature branch, commit, push, open a PR, and merge it after remote
769-
CI is fully green (see Multi-agent discipline: never straight to `main`,
770-
never `gh pr merge --auto`). A finished task = a merged PR, not a dirty
771-
working tree.
836+
create a feature branch, commit, push, open a PR, and — once remote CI is
837+
fully green and the PR is accepted — arm auto-merge so the queue lands it
838+
(Multi-agent discipline §7: never straight to `main`; never arm a PR that
839+
isn't green yet). A finished task = a merged PR, not a dirty working tree.
772840
3. **Add a changeset for feature work.** When the change is a feature or functional improvement, run `pnpm changeset` (or add a `.changeset/*.md` entry) describing it before committing. Pure bug fixes do **not** require a changeset.
773841
**Breaking changesets must carry their migration.** If the change removes or renames anything an author can write (a spec key, an export, a config field), the changeset body must state the FROM → TO mapping and the one-line fix — this text ships to consumers as `CHANGELOG.md` inside the npm package and is what an upgrading agent greps after the tombstone error. Removing an authorable spec key also requires a tombstone so the rejection itself carries the prescription — `retiredKey()` (`packages/spec/src/shared/retired-key.ts`) on a non-strict schema, or an entry in the relevant `UNKNOWN_KEY_GUIDANCE` / `*_RETIRED_KEY_GUIDANCE` map (see `object.zod.ts`, `ai/tool.zod.ts`) when the schema is `.strict()`. The changeset is one of fourteen surfaces a retirement touches — follow the `spec-property-retirement` skill (`.claude/skills/`) rather than reconstructing the kit, and note the two routes imply **opposite** liveness-ledger dispositions.
774842
4. **Added or removed a `packages/spec` export? Run `pnpm --filter @objectstack/spec gen:api-surface` and commit the result.** The `TypeScript Type Check` job diffs spec's built export surface against `api-surface/` (one shard per entry point since #5837); a new export makes the snapshot stale and turns the job red. It reads the **built `dist` declarations**, so `OS_SKIP_DTS=1` — the flag you reach for to make local builds fast — skips exactly the artifact the gate inspects, and the check passes locally while failing in CI. Same shape for the other generated-artifact gates in that job (`check:docs`, `check:skill-refs`, `check:react-blocks`), which read `src/` and so do reproduce locally.

0 commit comments

Comments
 (0)