From dbe1c8e137b46ee1d8807dd300b4cf1d6ad11985 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 03:58:11 +0000 Subject: [PATCH] docs(pm-skill): land the quota/credential incident facts and reconcile the body-entity read/write rows in platform-readings Merges the measured facts of the 2026-08-16 quota/credential incident and the 2026-08-18 pool-exhaustion readings into the API-quota section: the REST fallback is not executable from an MCP-only session, field slimming does not avoid the server-side field_values fan-out, WebFetch and git transport are the famine-degraded read paths, mid-session credential rotation kills the GitHub MCP server, and gradual auth propagation can fake a zero-issue lane. Reconciles the read-layer entity artifact with the write-layer angle-bracket loss as scoped, non-contradictory rows. Pays for the new rows by compressing existing rows in place; the file stays at its 134-line ratchet ceiling. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Rn7aaamsR99FXRqLcpL99q --- .../references/platform-readings.md | 133 +++++++++--------- 1 file changed, 67 insertions(+), 66 deletions(-) diff --git a/.claude/skills/pm-dispatch/references/platform-readings.md b/.claude/skills/pm-dispatch/references/platform-readings.md index de465d25c4..2a7b6f53ad 100644 --- a/.claude/skills/pm-dispatch/references/platform-readings.md +++ b/.claude/skills/pm-dispatch/references/platform-readings.md @@ -22,55 +22,64 @@ - **状态核验用最小字段**(search/list + `fields`)或等事件,整对象 `get` 留给入队 决策点;门禁放行判据 = 承载门禁族 job 的 conclusion,聚合(`blocked`/`dirty`) 只作阴性筛查再定位,放行按名定向读单条 job,⛔ 不拉全表(按名定位失败才拉)。 -- **PR 转回 draft 会同时掉 auto-merge 与队列成员资格,且不自动恢复**;转正后必须 - 重新挂。反方向同理:要真踢出队列,只有转 draft —— `disable_pr_auto_merge` 单独 - 调用**不解除队列成员资格**,PR 照样落地。 -- **`enable_pr_auto_merge` 一律显式传 `mergeMethod: "SQUASH"`**:不传时静默退回被 - 禁的 merge-commit 方式,等于无操作。**回显两向不可靠**(实测:队列路径回显空而 - 入队照发;显式传 SQUASH 回显 `MERGE`,队列侧改写,落地仍每 PR 一提交)⇒ 权威信 - 号只有 timeline 入队事件与最终 MERGED,⛔ 不拿回显当任何方向的证据。 +- **PR 转回 draft 同时掉 auto-merge 与队列成员资格,均不自动恢复**(转正后必须重 + 新挂);反方向同理:要真踢出队列只有转 draft —— `disable_pr_auto_merge` 单独调用**不解除队列成员资格**,PR 照样落地。 +- **`enable_pr_auto_merge` 一律显式传 `mergeMethod: "SQUASH"`**(不传时静默退回被 + 禁的 merge-commit 方式 = 无操作);**回显两向不可靠**(实测:队列路径回显空而入 + 队照发;显式传 SQUASH 回显 `MERGE`,落地仍每 PR 一提交)⇒ 权威信号只有 timeline 入队事件与最终 MERGED,⛔ 不拿回显当任何方向的证据。 - enable 后的验证序列:① 先验队列分支(给条目 ~20–30s 建出);② 分支在 ⇒ 结束, ⛔ 不翻转;③ 等待后仍缺席**且队列已见 churn**(更新的条目建出了分支而你的没有 —— - 截断下单纯缺席不充分)⇒ 翻转一次(`disable` → `enable`),翻转后仍以 timeline - 事件验证;④ ⛔ enable 与它的队列验证之间永不插 `disable` ——「入队」webhook 可能 - 乱序迟到,armed 窗口里补的 disable 会把已发生的真实入队撤掉。 + 截断下单纯缺席不充分;首挂静默不入队实测存在,churn 后翻转即愈)⇒ 翻转一次 + (`disable` → `enable`),翻转后仍以 timeline 事件验证;④ ⛔ enable 与它的队列验 + 证之间永不插 `disable` ——「入队」webhook 可能乱序迟到,armed 窗口里补的 disable 会把已发生的真实入队撤掉。 - **队列踢出先认签名再决定重投**:已知 flaky 核对失败签名一致 ⇒ 原样重投;止血修 - 复合入后**同一签名再现就不再是那条 flaky**,是新问题,必须重新诊断,⛔ 禁止条件 - 反射式重投。第三种签名:本 PR 名下**没有任何** `merge_group` run 且批次同伴的 - run 全部 `success` = 队列重建的连带取消,不是红 —— 带签名读数收据重投一次(收据 - 留在 PR 上),⛔ 无收据不重投;同一 PR 第二次被踢 ⇒ 停止重投,按签名四分支重判。 + 复合入后**同一签名再现就不再是那条 flaky**,是新问题必须重新诊断,⛔ 禁止条件反 + 射式重投;第三种签名:本 PR 名下**没有任何** `merge_group` run 且批次同伴的 run + 全部 `success` = 队列重建的连带取消不是红 —— 带签名读数收据重投一次(收据留在 + PR 上),⛔ 无收据不重投;同一 PR 第二次被踢 ⇒ 停止重投,按签名四分支重判。 - **实测吞吐参数两则**:合并队列落地延迟 ≈ 每 PR 15–30 分钟且串行(⛔ 不据「还没 落」提前判异常);单容器重验证(build+test)并发甜点 ≈3,排批按它定并发上限。 ## API 配额 -- GraphQL 配额(5000/时)极易打满,**MCP list 家族(`list_issues` 等)整个走 - GraphQL 池** —— 反复撞上的限流墙就是它;读与评论一律走 REST(core 15000/时,独 - 立计);只有无 REST 对应物的写才花 GraphQL,`issue_write` 连查找半边都吃 —— 配额 - 红时认领类动作整体排队,评论(REST)先行把结论发出去。 -- **git 先行**:能从本地检出 / `git log` / `ls-remote` 读到的状态不花配额;开轮先 - 读配额(免装 gh CLI:`curl -H "Authorization: Bearer $GH_TOKEN" +- GraphQL 配额(5000/时)极易打满,**MCP list/search 家族整个走 GraphQL 池** —— + 反复撞上的限流墙就是它;「读与评论走 REST(core 15000/时,独立计)」预设会话真有 + REST 通道:MCP 读工具无 REST 替身,直连 REST 受会话级授权门(会话起点快照,403 + `GitHub access is not enabled for this session`)与出口代理(只放 repo-scoped + 路径,`/search/*` 被拒、`/rate_limit` 例外)钳制 —— **纯 MCP 会话撞上枯竭池 = + 重置前没有任何 list 通道**,降级读法 = 下文 git 先行与 WebFetch 两行;只有无 + REST 对应物的写才花 GraphQL,`issue_write` 连查找半边都吃 —— 配额红时认领类动作排队,评论(REST 池)先行把结论发出去。 +- **`fields` 瘦身省载荷不省池**:MCP list/search 服务器端无条件抓 Project + field_values —— 池枯竭时**最小字段请求同样全体失败**,报错串 + `failed to fetch issue field values: API rate limit already exceeded`。 +- **git 先行**:本地检出 / `git log` / `ls-remote` 不花配额,断粮期分支存在性检查 + 照常可用,PR 文件读取同走 git(REST PR files 端点实测可瞬态 404);开轮先读配额 + (免装 gh CLI:`curl -H "Authorization: Bearer $GH_TOKEN" https://api.github.com/rate_limit`),graphql remaining < 1000 ⇒ 本轮降级为 git 先行 + 只做必要写。打满时:待执行写**排成有序清单挂进巡逻词**(不靠记忆),恢复 窗口按序连清;重试对齐整点(REST core 整点重置)优于指数退避,⛔ 绝不忙轮询; - search 与 core 独立计,一侧打满另一侧可作退路;REST core 共享身份下同样会打满 - ——「走 REST」≠「不限量」。 -- **MCP 参数两陷阱**:`list_issues` 多标签过滤是 **OR(并集)**不是 AND —— 双标签 - 查询混入别车道同状态卡与本车道全状态卡,结果良构、规模合理,失效全静默;正确读 - 法 = **整车道单标签一次读全 + 本地对 labels 求交**(正确性要求,不是风格偏好)。 - `issue_write` 的 `labels` 是**整组替换**不是追加 —— 写标签前**同一动作内**重读现 - 值合并再写,隔轮/隔小时的旧读数视为无效快照(按其回写会静默剥掉别的标签);真追加走 REST `POST /issues/{n}/labels`;写后照标签纪律回读。 -- **`list_issues` 永不返回 assignees**(`fields` 枚举无此成员;不传 `fields` 也没 - 有)—— 已认领卡与空闲卡在响应里逐字节相同,车道清单因此回答不了「哪张能认领」, - 失效完全静默。清单只是**候选名单**:每一条在认领前必须过一次完整 `issue_read` - (它才返回 `assignees`),⛔ 不把 `list_issues` 结果当候选集直接认领。 -- **MCP `issue_read` 的 body 实体转义是纯读侧伪影**(撇号/引号/尖括号成实体; - comments 原样),存储体未变;**先解码实体再写回**的往返实测安全(无双重转义), - 腐蚀 body 的是把转义读数原样回写。写侧真损耗:HTML 注释写入时被**静默剥除**,要存活的内容一律写成可见 markdown。 + search 与 core 独立计,一侧打满另一侧可作退路;REST core 共享身份下同样会打满;文档载明、未实测:条件请求答 `304` 不计 core 池(仅当直连 REST 获准才相关)。 +- **公开仓降级读法:WebFetch github.com 网页零 API 配额**(带 label 过滤的 issue + 列表、issue 全文含评论、PR 页含 checks,实测撑得起整轮盘点);边界:~15 分钟缓存、列表行不含 assignee、内容是渲染层。 +- **会话中途轮换凭据把 GitHub MCP 服务器杀到不可恢复**:此后一切 `mcp__github__*` + 回 `Streamable HTTP error: invalid session`(含几分钟前还好的工具),只有新会话 + 重绑 —— 轮换前提醒维护者:在飞席位丢的是整条 GitHub 通道;配额池按身份计,换身份即清零燃烧,共享身份结构不变。 +- **组织侧授权变更后仓库访问逐步传播**(同一端点数分钟内 403→200);403 错误对象存 + 盘仍是合法 JSON,期待列表的脚本会静默报假「0 issues」—— 零命中纪律覆盖 list 读:空车道先对仓库 `open_issues_count` 反查再信。 +- **MCP 参数两陷阱**:`list_issues` 多标签过滤是 **OR(并集)**不是 AND —— 混入别 + 车道同状态卡与本车道全状态卡,结果良构、失效全静默;正确读法 = **整车道单标签一 + 次读全 + 本地对 labels 求交**。`issue_write` 的 `labels` 是**整组替换**不是追加 + —— 同一动作内重读现值合并再写(隔轮旧读数 = 无效快照,按其回写静默剥别的标签);真追加走 REST `POST /issues/{n}/labels`;写后照标签纪律回读。 +- **`list_issues` 永不返回 assignees**(`fields` 枚举无此成员,不传也没有)—— 已 + 认领卡与空闲卡响应逐字节相同,清单只是**候选名单**:每条认领前必须过完整 + `issue_read`(它才返回 `assignees`),⛔ 不把清单当候选集直接认领。 +- **MCP `issue_read` 的 body 实体转义是纯读侧伪影**(撇号/引号/尖括号成数字实体; + comments 原样),存储体是明文,**先解码实体再写回**往返实测安全(无双重转义)—— + 腐蚀 body 的恰是把转义读数原样回写;可逆的只有读侧,写侧剥除(HTML 注释、短 + `<…>` 片段 —— 细则见「读数陷阱」截断行)是**真实存储损耗**,⛔ 两类不并成一条「API 会改 body」,写后回读因此必做;实体归属(MCP 还是 GitHub API)与 `&` 类未实测。 - **`Blocked-by:` 行归 BODY(单通道反向索引)**:追加按上条「解码后写回」执行;历 史上寄放在评论里的行按同程序**增量**回填(⛔ 不搞批量突击 —— 限流压力);解锁扫 - 描只 grep body,⛔ 不加常设评论读;旧「连评论一起扫(`in:comments`)」提示作废, - 扫描走直读(`list_issues` + `issue_read` 读 body)。 + 描只 grep body,⛔ 不加常设评论读;旧「连评论一起扫(`in:comments`)」提示作废,扫描走直读(`list_issues` + `issue_read` 读 body)。 - **`list_issue_types` 对本集成 403,而 `issue_write type:` 正常**(读权限缺口): 直接写已知好值(`Bug`/`Feature`/`Task`),写侧报错才是真信号,⛔ 不先探列表定可 用性(列表 403 ≠「类型不可用」);非法值是响错还是静默丢弃未实测,写非已知值前先小样验证。 @@ -78,11 +87,10 @@ ## 读数陷阱 - **读数四坑**:`cd X && cmd` 会短路(路径不存在时命令在当前仓继续执行,产出假读 - 数)—— 跨仓一律 `git -C `;`git grep -c | wc -l` 数的是文件数不是命 - 中数;裸名 grep 被幸存家族当子串命中 —— 退役核验带引号精确名,更硬的判据是查声 - 明式(`^(export )?(const|type|interface) \b`)而不是查提及;浅检出上的历 - 史读数不可信(`merge-base --is-ancestor` 假「非祖先」、`rev-list --count` 截 - 断、`branch -r --contains` 零输出)—— 先 `--deepen` 再判,或走 REST `compare`。 + 数)—— 跨仓一律 `git -C `;`git grep -c | wc -l` 数文件数不是命中数; + 裸名 grep 被幸存家族当子串命中 —— 退役核验带引号精确名,更硬判据是查声明式 + (`^(export )?(const|type|interface) \b`)而非查提及;浅检出上的历史读数不可信 + (`merge-base --is-ancestor` 假「非祖先」、`rev-list --count` 截断、`branch -r --contains` 零输出)—— 先 `--deepen` 再判,或走 REST `compare`。 - `rerun_failed_jobs` 复用原 run 的提交与合并 ref,不拿新 main 重算 —— 红因是基上 缺一个已合修复时重跑无效,只能推提交(`git merge origin/main`);判别:修复的合 并时间晚于 run 创建时间即是。 @@ -90,44 +98,37 @@ 预期签名,不是选择性失败**(cancel-in-progress 窗口只罩得住跑得慢的载体):先 比对 run `head_sha` 与 PR 当前 head(取代必有新 head),不开「为何只取消它」调查。 - **CI 红了先取完整日志归档再下结论**:「completeness check 绿」只断言没有 worker - 静默死,≠ 测试通过;并发输出的「相邻」≠「因果」(先查 `turbo.json` 依赖边); - ⛔ 不只看 tail。公开发出的诊断被推翻时,更正发在同样公开的位置,据它开的 PR 撤回 - draft、解绑 `Fixes`。 + 静默死 ≠ 测试通过;并发输出的「相邻」≠「因果」(先查 `turbo.json` 依赖边),⛔ 不 + 只看 tail;公开发出的诊断被推翻时,更正发在同样公开的位置,据它开的 PR 撤回 draft、解绑 `Fixes`。 - **判「正文被截断」必须双读取**:`.body` 原文 + `Accept: - application/vnd.github.full+json` 的 `.body_html`,两者在同一处断掉才算 issue 端 - 截断;任何单一读法的尾部缺失先算读取端截断(工具输出上限、分页、切片)。写侧另 - 一半:sanitizer 会在**写入时就地删除**短 `<…>` 片段(HTML 注释标记、`` 类占 - 位符、泛型),反引号与围栏**不提供保护** —— 要保留字面尖括号一律写 HTML 实体 - `<` / `>`;含这类片段的正文,写后回读逐个确认仍在(失效完全静默)。 -- **并行 spec PR 同动 pin 计数断言**(被踢不是事故,按 os-regen 序再解一轮):解 - 冲突两侧收据都保留、按合并顺序堆叠,新计数**从合并后源码重数**(操作数是文件本 - 身,不是历史),⛔ 不从两侧收据做算术;双方占同一编号是常态(各取 - 当时 max+1),重编号后进侧。 + application/vnd.github.full+json` 的 `.body_html`,两者同一处断掉才算 issue 端截 + 断,单一读法的尾部缺失先算读取端截断(工具输出上限、分页、切片)。写侧另一半: + sanitizer 会在**写入时就地删除**短 `<…>` 片段(HTML 注释标记、`` 类占位符、泛 + 型),反引号与围栏**不提供保护** —— 要保留字面尖括号一律写 HTML 实体 `<` / `>`,要存活的注释类标记改写成可见 markdown;含这类片段的正文,写后回读逐个确认仍在(失效完全静默)。 +- **并行 spec PR 同动 pin 计数断言**(被踢不是事故,按 os-regen 序再解一轮):解冲 + 突两侧收据都保留、按合并顺序堆叠,新计数**从合并后源码重数**(操作数是文件本身不 + 是历史),⛔ 不从两侧收据做算术;双方占同一编号是常态(各取当时 max+1),重编号后进侧。 - **容器重启杀死在飞 dev,现场三态判读**:① 分支已推 + PR 已开 ⇒ 只欠验收(CI 重 跑 + 复核,不动代码);② 死在 regen 中途(未提交全是生成物、merge commit 已在) ⇒ PM 直接续作 —— build → 整链 regen → 生成物门禁全绿 → 提交推送,恢复 commit 带 `Recovery commit:` 前缀留审计;⚠️ 有的现场 regen 一件没跑,推送前先跑生成物门禁 别赌;③ 死在源码编辑中途 ⇒ 先读 diff 判完整性 —— docblock 写全动机/失效模式/判 - 据的,PM 可代跑终验后提交;写一半意图不明的 ⛔ 不代提交,记进交接。dev 临时目录 - (`.os-scratch/` 一类)是工作物不是交付物,清掉,⛔ 不进 feature PR。 + 据的,PM 可代跑终验后提交,写一半意图不明的 ⛔ 不代提交、记进交接;dev 临时目录(`.os-scratch/` 一类)是工作物不是交付物,清掉,⛔ 不进 feature PR。 ## 闭合关键词解析(PR 正文写侧) -- **PR 正文里「不修某卡」的否定句会关掉那张卡**:GitHub 的闭合关键词解析器匹配 +- **PR 正文里「不修某卡」的否定句照样关卡**:闭合关键词解析器匹配 `fix/fixes/fixed/close/closes/closed/resolve/resolves/resolved` + `#N`,**不理会 - 前面的否定词** —— 声明不修的那句话恰恰在合并时关卡。安全写法:把号码放在没有关 - 键词打头的位置 —— `#N is not addressed here` / `out of scope: #N` / - `#N remains open`。实测解析边界三条:关键词只绑**同一行**的 `#N`;动名词 - (closing/fixing)不是关键词;行内反引号里的关键词不触发(code span 实测不建闭 - 合链接;围栏块未独立实测,按同规则对待但留待复测)。 -- **PR body 与 squash commit message 是两个独立解析源**:commit message 干净不代 - 表 body 干净 —— 只查 commit 会漏。误关的卡以 `completed` 状态对一切「只看 - open」的过滤与巡检隐身,没有任何机械守卫覆盖这条路径;消费侧检查 = 合并后读 - `closed_by_pull_requests`(在复核清单)。 + 前面的否定词** —— 声明不修的那句话恰在合并时关卡;安全写法 = 号码不被关键词打头 + (`#N is not addressed here` / `out of scope: #N` / `#N remains open`)。实测边 + 界三条:关键词只绑**同一行**的 `#N`;动名词(closing/fixing)不是关键词;行内反引号里的关键词不触发(code span 实测不建闭合链接;围栏块未独立实测,按同规则对待但留待复测)。 +- **PR body 与 squash commit message 是两个独立解析源**(commit 干净 ≠ body 干净, + 只查 commit 会漏);误关的卡以 `completed` 状态对一切「只看 open」的过滤与巡检隐 + 身,无任何机械守卫覆盖这条路径 —— 消费侧检查 = 合并后读 `closed_by_pull_requests`(在复核清单)。 ## 断粮检测与跨墙恢复细则(5 小时用量墙) 原则、定时器选型(⛔ 不用 send_later 链)与恢复 playbook 在主文件;事实补遗: -`npx ccusage blocks` 容器内可用(读本地会话记录),给当前 5 小时窗口边界/剩余时间 +`npx ccusage blocks` 容器内可用(读本地会话记录),报当前 5 小时窗口边界/剩余时间 与燃烧率(预警);第三盲区:窗口起点是本地推断的近似值;撞墙时 API 调用失败、宿主 报「limit reached, resets at HH:MM」(重置时刻主文件已述:那一刻可得、记下来)。