@@ -68,6 +68,58 @@ gh label create repo:cloud -R objectstack-ai/objectstack -c c5def5 -d "Lands
6868(Use the GitHub MCP tools instead of ` gh ` when the CLI is unavailable — the
6969protocol is identical.)
7070
71+ ## Operational notes(实测坑位)
72+
73+ 队列与平台层的四条实测结论。共同点:** 判据取命令的输出,不取 API 字段的字面值,
74+ 也不取本地工作树的现状** —— 每一条都是在这一步上咬过人之后写下来的。
75+
76+ ** 1. 判断 PR 是否在合并队列,看 ` gh-readonly-queue/* ` 分支,不看 ` auto_merge `
77+ 字段。** 本仓 PR 入队后,REST 返回的 ` auto_merge ` 回落为 off(队列条目取代了挂起的
78+ auto-merge),该字段对「在不在队列里」零信息量,据它反推会得出「没入队,再入一次」
79+ 的错误结论。唯一判据:
80+
81+ ``` bash
82+ git ls-remote --heads origin ' refs/heads/gh-readonly-queue/*'
83+ ```
84+
85+ 分支名里带着这一批被打包的 PR 号;没有匹配分支才是真的没入队。
86+
87+ ** 2. 队列踢出:先认签名,再决定重投。** 被踢出不等于 PR 有问题。今天 #4796 那条已知
88+ flaky 连踢五个互不相关的 PR,核对失败签名一致后原样重投,五个全部一次通过 —— 认签名
89+ 的成本远低于逐个改 PR。但反过来是同等硬度的规则:止血 PR(#4856 )合入后,** 同一签名再次
90+ 出现就不再是那条 flaky** ,已修签名的再现是新问题,必须重新诊断,禁止条件反射式重投。
91+ 给 flaky issue 追记命中次数,只在新信息改变修法作用域时才值得(第 3/4 次命中把靶面从
92+ 一条用例扩到整个模板,值得记;纯计数不值得占用 issue 时间线)。
93+
94+ ** 3. GitHub MCP 的 GraphQL 配额(5000/时)极易打满,读操作与评论一律走 REST。**
95+ 今天三次归零(峰值 10402/5000),每次卡死的都是 ` enable_pr_auto_merge ` 、draft 状态
96+ 切换、` list_issues ` 这几个 GraphQL-only 操作 —— 配额一空,整个循环停在复核与入队上。
97+ 规程三条:
98+
99+ - 读与评论优先 ` curl ` / ` gh api ` 走 REST(core 配额 15000/时,与 GraphQL ** 独立计** ),
100+ 只有确实没有 REST 对应物的写操作才花 GraphQL 配额;
101+ - 配额打满时把 GraphQL 写操作** 排队而不是重试** ,后台轮询 ` rate_limit ` 的
102+ ` resources.graphql.remaining ` ,恢复即执行:
103+
104+ ``` bash
105+ gh api rate_limit --jq ' .resources.graphql' # 或 curl https://api.github.com/rate_limit
106+ ```
107+
108+ - 复核意见不等配额 —— 先用 REST 评论把结论发出去,入队、切 ready 这类 GraphQL 动作
109+ 事后补;维护者拿到的信息不该被配额延迟。
110+
111+ ** 4. 核验 main 的事实用 ` origin/main ` ,不用共享检出的工作树。** 共享检出的 HEAD 由
112+ 别的 agent 摆布,可能落后 origin/main 数十提交(今天 PM 与一名 dev 都在落后 63 提交的
113+ 树上 grep 出假阴性,据此差点判了错误的结论)。一律先 ` git fetch origin main ` ,再:
114+
115+ ``` bash
116+ git grep < pattern> origin/main -- < paths> # 而不是在工作树里 grep
117+ git show origin/main:< path> # 看某个文件在 main 上的现状
118+ ```
119+
120+ 这条也写进派发词(step 5): dev 在自己的 worktree 里同样会踩,而 worktree 是从
121+ ` origin/main ` 切的、后续不会自己更新。
122+
71123## Multi-repo coordination (backend / frontend / cloud)
72124
73125The product spans three repos with a fixed dependency direction:
@@ -333,6 +385,18 @@ routing isn't already decided:
333385 branch, no PR is invisible — that is what the claim-first rule (step 4)
334386 exists to shrink.
335387
388+ ** Recognize the "no producer" shape —「生产者在哪?」is a standing triage
389+ question.** One issue class is invisible to every automated check: a field is
390+ declared, consumers read it, types and gates are fully green — and ** no code
391+ path ever writes it** . Five hits in one day: #4704 (` Seed.env ` , six call sites
392+ drop it), #4837 (the liveness ledger's own criterion), #4839 (` session.roles `
393+ written nowhere in the repo), #4862 (flow triggers bulk-set ` previous ` without
394+ binding it), #4867 . Type systems and lint validate the ** consumer** side only,
395+ so a missing 生产者 survives indefinitely under a green tree. On any issue
396+ shaped ` declared ≠ enforced ` , ask where the producer is before routing it —
397+ the answer is usually the root cause, and it changes the issue's scope (and
398+ often its ` domain:* ` label) * before* dispatch rather than in the dev's report.
399+
336400### 3. Select the batch
337401
338402Pick up to ` batch ` issues that are ** mutually independent** : no two issues in
@@ -343,6 +407,17 @@ the next round. Prefer small, well-specified issues; an issue with no acceptance
343407criteria you can state in one sentence is a candidate for escalation (step 8),
344408not dispatch.
345409
410+ ** Same-file issues serialize strictly across rounds — and deferring is not
411+ shelving.** Two issues on one file ride in different rounds, no exception
412+ (#4820 /#4821 ). The part that is easy to miss: while #4820 was in flight its dev
413+ established that the fix #4821 's body proposes (a ` JSON.stringify ` key) would
414+ change type-coercion semantics and introduce a fresh silent defect. That
415+ warning ** and** the ` Blocked-by: ` line were written onto #4821 in the same round
416+ #4820 's review closed — not the next one. A deferred issue sits in the queue
417+ looking dispatchable to every sweep, including another PM's; whatever you
418+ learned about it is worthless until it is on the issue. Rule: when step 3 pushes
419+ an issue to a later round, record the known trap on it before the round ends.
420+
346421### 4. Claim
347422
348423All agents share one GitHub identity, so the assignee alone says "some agent
@@ -428,6 +503,42 @@ Follow your operating procedure (you are the os-dev agent). Non-negotiables:
428503Return ONLY the JSON report defined in your agent definition.
429504```
430505
506+ ** Same-day churn on the issue's files goes INTO the prompt.** Step 1's
507+ stale-premise check protects against issues that aged; the same-day variant is
508+ main moving between filing and dispatch on the very file the issue quotes —
509+ #4808 was dispatched right after #4806 rewrote the same guard, #4820 right
510+ after #4822 touched the same file. Both prompts carried an explicit line
511+ (「基于合并后的代码工作,issue 引用的片段可能已变,先核对当前 main」), and both
512+ devs avoided rework that the issue's own snippets would have caused. Add that
513+ line whenever ` git log origin/main --oneline -20 -- <paths> ` shows a merge on
514+ the issue's files today, and tell the dev to verify against ` origin/main `
515+ rather than any working tree (Operational notes 4) — the dev's worktree is cut
516+ from ` origin/main ` once and never refreshes itself.
517+
518+ #### Handing off an interrupted dev(worktree 接手协议)
519+
520+ ` /compact ` , and any host-level interruption of the PM session, kills running
521+ subagents together with their pending tool calls — #4700 and #4775 both died on
522+ the same second. ** The agent is not resumable; its worktree, branch and commits
523+ are intact.** Never re-run the original dispatch prompt over that state: a fresh
524+ agent that follows it will try to create the worktree that already exists, or
525+ redo work already committed. Dispatch a ** new** agent with these four additions
526+ instead:
527+
528+ - 「worktree ` <repo>-issue-<n> ` 已存在,⛔ 不要新建,` cd ` 进去接着做」— the
529+ worktree-first rule is already satisfied; creating a second one splits the work;
530+ - read every existing commit ** and** uncommitted change there ** first** , then
531+ decide per hunk to keep or amend it — neither restart from scratch nor trust
532+ it blindly (the dead agent never reported, so nothing about it is verified);
533+ - re-run the verification the dead agent never reached, in full, and report its
534+ real output — an interrupted run leaves no test evidence at all;
535+ - the assignee, claim comment and branch stay untouched: this is a continuation
536+ of the existing claim, not a new one, and the claim comment records the
537+ handoff rather than being replaced.
538+
539+ Both #4700 and #4775 passed review on the first try after being handed off this
540+ way.
541+
431542#### Resource limits — parallel agents share ONE container
432543
433544Memory peaks come from ** build + test** , not editing, so the fix is not less
@@ -493,6 +604,29 @@ against the report's own claims:
493604- Test evidence in the report shows the actual commands and passing output,
494605 not a bare "tests pass".
495606- The diff plausibly satisfies the issue's acceptance criteria.
607+ - ** Did the dev verify the issue's premise?** A report that falsifies the
608+ issue — or your own dispatch prompt — is a sign of a * good* run; a report
609+ that accepts every stated cause at face value is the one to read twice. Four
610+ same-day cases: #4808 (the issue was half right — the real truncation was in
611+ pruning, not the TTL), #4813 (the technical rationale the PM supplied was
612+ disproved by measurement and the dev's was harder; the issue body's wrong
613+ attribution became #4873 ), #4825 (the issue's option 2 was killed by
614+ call-site evidence), #4790 (previous day, a fixed-window conversion
615+ rejected). When a dev corrects the PM, ** acknowledge it in the open** — the
616+ correction belongs in the PR/issue comments so the next reader inherits the
617+ corrected premise, and a wrong premise still sitting in an issue body gets
618+ its own follow-up issue rather than being silently dropped.
619+ - ** ` +0/-0 ` in a PR diff is not proof of an empty file.** git renders a file
620+ as binary — zero added, zero removed — as soon as it contains a NUL byte.
621+ #4870 's 347-line test file showed ` +0/-0 ` and was briefly misread as an
622+ unfinished placeholder; it was one bare ` 0x00 ` in the body, which
623+ ` pnpm check:nul-bytes ` rejects by design. On any ` +0/-0 ` entry suspect NUL
624+ first and an empty file second, and settle it on the blob rather than the
625+ diff (` git show <sha>:<path> | wc -l ` ). The fix always belongs in the
626+ source: write the escape sequence ` \u0000 ` , which is byte-identical at
627+ runtime and is the convention ` scripts/check-nul-bytes.mjs ` enforces. A
628+ raw NUL is never the right authoring choice: it also makes the whole file
629+ invisible to grep, which is how the defect hides in the first place.
496630
497631Verdict per issue:
498632
0 commit comments