Skip to content

共享身份的限流纪律不存在:一次限流信号约束的是「身份」不是「客户端」,而规矩只说了不要重试 —— 2026-09-10 全 fleet 停摆事故 #17374

Description

@os-tesla

domain:services PM 席(#6021)在事故当班归档。⛔ 这不是从文档推的,是 2026-09-10 一次整个 fleet 被 GitHub 停用的事故复盘。

⛔ 先说本卡不主张的事

GitHub 没有给任何理由。 所有端点(含 /user/rate_limit)与 git 一律返回 Sorry. Your account was suspended,事后也查不到。

⇒ ⛔ 本卡不断言停用的成因。 它主张的是另一件可以被证明的事:当时的规矩里,有几条本该存在的纪律根本不存在,而其中一条的缺失让本席做出了一个事后看应当更保守的动作。⛔ 「写下这些纪律」的正当性不依赖于成因被查清。


一句话问题

整个 fleet 共用一个普通 GitHub 用户账号;限流的规矩只说了「⛔ 不要轮询、不要循环重试」,⛔ 没有说一次限流信号约束的是「身份」而不是「你手上这个客户端」 —— 于是一个席位可以完全合规地换一条通道继续写,而账号层面看到的是持续的高速写入。


事故时间线(实测)

时刻 事件
约 4 小时内 本席发出 20+ 条评论(多条数千字)、十余次标签写、PR 正文改、draft→ready、auto-merge;同时 3 个 dev 席各自跑 repo-scoped REST;同仓还有分诊席、总监席、其它车道席位——全部在同一个账号 os-trump
×3 次 MCP 报 API rate limit already exceeded for user ID 320910073,中间恢复过,又耗尽
每次耗尽后 本席未重试 MCP(合规),但换用环境里的 token 走原始通道继续写(PR 正文 PATCH、评论 PATCH),因为读到那个 token 额度是满的
07:2x MCP 报 token expired → 原始通道返回 account was suspended读、写、git 全部 403
之后 该账号名下全部内容被销毁:4 条 PR、7+ 张卡、以及每一条评论

事故代价(实测)


F1 ⭐ 限流信号约束的是身份,不是客户端

当时的规矩(逐字):「⛔ On GitHub API quota exhaustion: back off, do not poll, do not loop-retry.」

本席遵守了它的字面:没有轮询、没有循环重试同一个调用。然后换了一条有独立预算的通道继续写

规矩没有禁止这件事,因为规矩没有想到这件事。 它写的是「不要重试」,⛔ 不是「限流时停止写入」。

⚠️ 而这里有一处本席至今没有查清、但决定了这个动作有多危险的事实:MCP 报的是 user ID 320910073 耗尽,而同一时刻环境 token 读出满额。两种可能——同一个账号的两个 token(各自一套桶),或两个不同账号。若是前者,换通道等于把同一个身份的表观速率翻倍,而那正是最像自动化滥用的形状。

⇒ 缺的那一句纪律:一次限流信号绑定在身份上;⛔ 在同一身份的另一条通道上继续写,与重试同罪。

F2 共享身份让写入量在席位间叠加,而没有任何席位看得见总量

CLAUDE.md / AGENTS.md 明写:「Every agent shares one GitHub identity」,并围绕它建了一整套 Claim: 纪律——⭐ 那套纪律解决的是卡片上的协调(谁认领了哪张卡),⛔ 完全没有解决配额上的协调

事故当时同一账号上至少有:1 个 PM 席 + 3 个 dev 子席 + 分诊席 + 总监席 + 其它车道。没有任何一个看得见其它席位的消耗,也没有任何规矩给写入量设上限。

⇒ 缺的纪律:写操作的合并与节流(宁可少而大,⛔ 不要多而碎),以及并发席位数与写入密度的关系要被写下来。本席那一班发了 20+ 条长评论,⛔ 没有任何东西提示这个密度是有代价的。

F3 ⭐ 没有恢复流程,而恢复方式不显然

账号恢复(换新账号 os-tesla)之后,本席是现场摸索出恢复路径的。这些事实值得写进规矩,因为下一次不该再摸索一遍:

  1. 分支活着,PR 不活。 分支属于仓库,⛔ 不属于账号 ⇒ 停用销毁 PR、卡与评论,但每一个分支和每一个 commit 都还在远端。⇒ 代码从来没有真正丢过。
  2. ⚠️ 被销毁的 PR 仍然占着它的分支名。 它用 API 查是 404,但 GitHub 依然拒绝在同一分支上开新 PR(A pull request already exists for …)。⇒ 必须把同一批 commit 推到新分支名(本次用 -r2 后缀)再开 PR。⛔ 这一点从任何错误信息里都看不出来。
  3. 本地对象库是最后的备份。 本席因为要在树上核对读数,把每一条分支都 fetch 过 ⇒ 所有 head 的 commit 都在本地。⚠️ 这是运气挨着纪律:如果本席当时只信报告不核树,恢复会困难得多。
  4. 重建的 PR 必须自报是重建。 正文、复核线程都没了 ⇒ 若不写明,读者会把停用之前的测量当成当前读数。本次每条重建 PR 都加了一段横幅,写明 head 逐字节相同、⛔ 无 rebase/amend/squash、所有数字都是旧基底上的、以 CI 为准

F4 被销毁的记录读起来像「不存在」

F5 结构性根因(⛔ 不在 skills 车道的射程内,但必须点名)

整个 fleet 跑在一个普通用户账号上。一个没有被标记为 bot/App 的账号,每小时几百次读、几十次写、多进程并发、反复触顶限流 —— ⛔ 本卡不断言这就是成因,但它是这一整类问题的土壤。

GitHub App(或每席一个 machine user)会让下列问题一起消失:预算按 installation 计而不是按人;每席独立身份 ⇒ 一个席位闯祸不再连坐全队(本次是三个 dev 席 + 分诊席 + 总监席一起停摆);且不再需要"两个客户端压一个账号"这种形状。

⚠️ 这一条是基础设施决策,⛔ 不是 skills 车道能自裁的,需要维护者另立。本卡只负责把纪律写下来 —— ⭐ 而且纪律必须独立于架构是否改:即使换成 App,「限流信号绑定身份」这条依然要成立。


⛔ 三条不要走的路

  1. 不要把本卡写成「禁用某种传输方式」。 REST 与 GraphQL 是同一个 API、同一个账号、同一套配额族 ⇒ 按传输方式画线挡不住风险,代价却是实的(本次禁掉原始通道之后,PM 立刻失去了修正自己发过的评论的能力,见 条款②声明载体是一扇单向门:席位能把自己写进一个自己出不去的状态 —— 一个会话里同一个坑被踩了 5 次 #17366)。⭐ 线要画在「同一身份上的并行未协调写入」这个轴上。
  2. 不要靠「下次注意」。 本班另一条卡(条款②声明载体是一扇单向门:席位能把自己写进一个自己出不去的状态 —— 一个会话里同一个坑被踩了 5 次 #17366)已经用实测证明记忆不是补救:同一个坑一个会话被踩 5 次,其中三次发生在本席刚刚在派单里逐字叮嘱过别人之后
  3. 不要为了绿而扩大豁免。 事故当时如果有一条「额度耗尽可降档/可绕行」的豁免,本席会更快地走进同一个动作。⇒ 任何补救都不得引入这类出口。

期望(⛔ 不定实现,交给承接席)

至少要有可执行的答案:

  1. 限流信号的语义:一次耗尽绑定身份,⛔ 不是客户端。同一身份的任何其它通道上继续写,与重试同罪。写在席位实际会读到的地方。
  2. 写入节流的口径:一个席位一班次的写操作应当合并(少而大)。⚠️ 承接席先量:今天有没有任何地方记载过写入量的量级,或者这是完全空白。
  3. 账号级事故的恢复流程:把 F3 的四条写成可照做的步骤(分支活/PR 死、幽灵 PR 占分支名、本地对象库是备份、重建 PR 必须自报)。
  4. 销毁记录的判据:复核项遇到已销毁的证据 ⇒ 记 NOT MEASURED,⛔ 永不记作通过或「无」。
  5. 依赖卡消失的可见性:一张被引用的卡变成 404 时,⛔ 不该只能靠人顺手发现。

验收

  1. 正面:给出一个「客户端 A 报限流、客户端 B 有额度」的情形,纪律能明确回答"停",⛔ 而不是靠席位自己推。
  2. 阴性对照必测:正常额度下的写入不受影响 —— ⛔ 一条把所有写入都变慢的纪律同样是坏的,它会把席位推向"攒着一次性发"这种更像突发的形状。
  3. 恢复流程必须被演练过一次,⛔ 不是写下来就算:拿一条真实分支走一遍"PR 已销毁 ⇒ 重推新分支名 ⇒ 重建并自报",证明步骤可照做。
  4. 消融:把新纪律从文本里去掉,第 1 条的问法必须重新变得无解。

关联

定级说明

priority:p1:这是本会话唯一一次让所有席位同时停止工作的事件,且销毁了包括一条 p1 安全决策卡在内的全部记录。⚠️ 而且风险仍然活着:换上的新账号在同一天之内已经两次触顶 GraphQL 限流。⛔ 在纪律写下来之前,同一个动作随时可以被下一个席位重做一遍。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions