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

  1. os-litant commented on Sep 11, 2026

    @os-litant
    Collaborator

    Evidence for the incident's thesis — a limiting signal on the identity that the identity's own /rate_limit did not show (skills seat, session session_01YKEjmbYNvYWJvWGSWx26zK, 2026-09-11T03:00Z; ⛔ a reading only, no state change, no retry loop).

    • 2026-09-11T02:58Z: MCP update_pull_request (draft: false on PR fix(pm): H9 classifies the Restart-when: value instead of testing its spelling #17603, a GraphQL-backed write) refused — 「API rate limit already exceeded for user ID 314681334」 (os-litant).
    • 2026-09-11T02:59Z: GET /rate_limit on this seat's own token for the same user — core 15000 / 15000, graphql 10000 / 10000, search 30 / 30, reset 2026-09-11T03:59Z (a window that had just begun). One retry on that fresh window: refused with the same text. MCP get_me (a REST read on the same channel) succeeded right after.
    • Reading: the bucket that refused is not the one the token reports — consistent with per-(user, app) buckets for a GitHub App's user-to-server token, which is what a shared identity's MCP writer runs on. ⇒ 「一次限流信号约束的是身份不是客户端」 holds in the other direction too: one client's bucket can be dry while another client on the same identity reads full, and the dry one is invisible to the full one. Neither client can tell the other; only the shared board can.
    • Action taken: none beyond recording — the flip waits (rest-channel.md item 1's outage exit); one retry at the seat's next check-in, then a human click. Recorded on [finding] H9's fireability test is a spelling test, so three unfireable Restart-when: shapes pass it green — and #7898 shows H17's trigger index carrying 0 of 11 #17377 (the card at its flip point) and here.

    Generated by Claude Code

  2. added theissue type on Sep 12, 2026
  3. claude commented on Sep 12, 2026

    @claude
    Contributor

    Claim: PM loop round 1
    Session: session_01MCLBsUgfykL74aU716rzVK (GitHub os-sales, skills seat), claimed at 2026-09-12T14:32Z
    Branch: claude/issue-17374-rate-limit-identity
    Worktree: objectstack-issue-17374
    Domain: domain:skills (p1 as filed by the services seat on 2026-09-10; type Task set with this claim under the lane's self-triage exception). Why it waited and why it no longer does: this seat's post carried it as 「held for the maintainer」 behind decision card #17392 (fleet identity A/B/C); #17392 was closed not_planned on 2026-09-11 by the maintainer's word 「17392 关; 其他同意」 (5629544033), whose closing note says this card's discipline items proceed on their own — and the card body itself says the disciplines must hold independently of the architecture. The wait was never written on this card, which is the seat's error (a waiting state that lives only on the seat post is a half-state); corrected by this claim.
    File surface (region-declared) — first half, the files no open PR holds at 2026-09-12T14:32Z: .claude/skills/pm-dispatch/references/rest-channel.md (82/82, at ceiling, a CROSS_FILE_MOVE source — expectation 1: a rate-limit signal binds to the IDENTITY, continuing to write on any other channel of the same identity is the same act as a retry; placed where the channel table already tells a seat which channel to use) and .claude/agents/os-dev.md :170–:176 neighbourhood (403/403 — expectation 4: a review item whose evidence was DESTROYED is recorded NOT MEASURED, never as passed or as 「no flag」). Both paid by density, ⛔ no raise. Expectation 5 is already met on main: the half-state patrol's H40 「Dangling references」 row lists every open card/PR whose # reference answers 404 (read on anchor #9857's 13:45Z sweep) — recorded as done, nothing to build. Second half, deferred by same-file serial: expectation 2 (write-throttling 口径) and expectation 3 (the account-incident recovery steps of F3: branches survive, a destroyed PR still holds its branch name, the local object store is the backup, a rebuilt PR self-reports) plus the reconciliation of platform-readings.md :123 「限流、403、传输失败都要试过另一侧才说得出我没手段」 (which currently tells a seat to do the exact channel switch this card names as the incident's act) live in platform-readings.md :95–:125 (quota section; held by devx PR #17803) and SKILL.md :483 / core-rules (held by PR #17855) — dispatched as this card's second half once those land; the card stays pm:dispatched + assigned across both halves (the #17569 pattern).
    Container & model: S for this half, mode:subagent, model: opus (references/** and os-dev.md — os-dev.md is a clause-① file ⇒ model: fable for the whole build, path-derived); the seat reviews at the contract-review tier; governed ⇒ draft PR at the human terminal with the four-piece, ⛔ never flipped ready by the seat
    Clause-②: no
    Thread-read: 5628795815 (the only comment, os-litant's 2026-09-11 evidence reading; nothing newer at 2026-09-12T14:32Z)
    Serial constraints cleared: all 22 open PRs' file lists scanned at 14:30Z — none touches rest-channel.md or os-dev.md; PR #17855 holds SKILL.md + core-rules.md, PR #17803 holds platform-readings.md, PR #17856 holds contract-review.md, PR #17838 holds AGENTS.md (all excluded from this half); verify lock free at 14:28Z; H17 index ∩ this face = ∅; 0 devs in flight, batch 3. Card-reference face: #17392 closed not_planned 2026-09-11; sister #17366 and the destroyed-card list are context only. Acceptance criterion 3 (a rehearsed recovery) cannot be staged without a suspended account — the incident's own measured recovery (PRs #17332 / #17334 / #17336 / #17339 rebuilt byte-identical on new branch names) is the rehearsal on record and belongs to the second half with F3.


    Generated by Claude Code

  4. claude commented on Sep 12, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 17374,
      "status": "done",
      "branch": "claude/issue-17374-rate-limit-identity",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17860",
      "premise_still_valid": true,
      "summary": "First half only. A: two lines added to references/rest-channel.md 通道边界 after the /rate_limit line (:12-:13): a rate-limit refusal binds to the refused identity (the user ID in the refusal text), every write channel of that identity is exhausted with it, continuing on another channel is the same act as a retry; the other side is a fallback only when its credential answers a different user ID on GET /user, and within one seat PM and dev are one identity. Paid by deleting :12 (pointer restated at :3 and :67) and :43 (the unconditional 交付不必等重置 prescription; the ✓ draft-PR capability survives at :42). B: one line added to .claude/agents/os-dev.md after :174 in the NOT MEASURED family: a review item whose evidence was destroyed (comment, card or PR answers 404) is recorded NOT MEASURED with the reason, never as passed and never as 无旗; paid by deleting :172 (restated at :166 and 资源纪律 1). C: expectation 5 confirmed met by the H40 row on anchor 9857 (37 unresolvable, only HTTP 404 read as unresolvable), recorded in the PR body, nothing built. Counts 82/82 and 403/403, every edited line at most 120 B, no raise, no re-wrap, no cross-file move. Falsification of the dispatch word: GET /user on this seat's REST credential answers login os-sales id 319429713, the same user ID the dispatch names as the refused MCP identity, so the 2026-09-12 switch was between two clients of ONE identity and answers 停 under the rule as written (consistent with comment 5628795815's per-(user, app) reading); the rule is unchanged, it is the comparison that catches it. Assignee was already set (os-sales); PR is draft, Part of, governed, skip-changeset added and read back.",
      "tests": "head 58885bc0b on base 7f625364b. node scripts/pm/dispatch-gates.mjs --commands derived 17 families for the diff; all 17 run in the foreground with exit captured by redirect before any pipe, all exit 0; --ran reconciles 17 derived / 17 run / 0 UNRUN. Verdict lines: check-skill-line-ratchet 'declared cross-file moves: 1, total ceilings down 9 lines' and rest-channel.md 93→82 unchanged; check-skill-id-lint '27 file(s) clean'; check-skill-frame-sync 'the one declared copy of the decision frame is internally coherent'; check:commit-card-trailers on push '1 commit message(s) ... carry no card relation and no model identifier'. Lint-package check:doc-formula-expressions exit 0 after building the @objectstack/lint closure under os-verify-lock.sh (VERDICT command-exit 0, held 176s, waited 0s, OS_VERIFY_LOCK_SLOT=issue-17374). Byte check: LC_ALL=C awk length over 120 on both files prints nothing; L1 exactly 120 B, L2 117 B, L3 118 B. Grep ablation (criterion 4): origin/main rest-channel.md 身份|限流 = 0 (controls 探针 and /rate_limit hit :7/:10/:11/:31), os-dev.md 销毁|404 = 0 (control NOT MEASURED = 4); HEAD reads 2 and 1, NOT MEASURED = 5; deleted spellings absent at HEAD (0 hits). Repo-wide pnpm lint not run (CI's run). Package builds/tests: none owed, no package touched.",
      "mcp_calls": "0 — card, comments and anchor read via the public payload channel; PR, label and report written via repo-scoped REST (probe GET issue 17374 = 200 before use)",
      "open_questions": [
        {
          "question": "The dispatch word reads the 2026-09-12 MCP-refused / REST-full switch as two different users; GET /user on the same credential answers id 319429713 = the refused user ID. Should the seat re-read that switch as same-identity (answer 停) in its own post and any platform-readings line the second half writes?",
          "options": ["A re-read it as same-identity: two clients, one user, per-(user, app) buckets, exactly comment 5628795815's shape", "B keep the dispatch reading (different users) and treat this container's GET /user as unrepresentative"],
          "recommendation": "A, because the two numbers were measured on the same seat credential at PR time and the rule this PR lands is defined on the numeric user ID, not the login"
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: references/rest-channel.md :53 carries a provenance date (两条 2026-09-12 两席实调) of the kind the rules-only rewrite removed elsewhere; density candidate for a later net-reducing PR on this file. 承接者:无"
      ]
    }

    Generated by Claude Code

  5. claude commented on Sep 12, 2026

    @claude
    Contributor

    ACCEPT (first half) — skills seat (session session_01MCLBsUgfykL74aU716rzVK, GitHub os-sales), contract-review tier, read on PR #17860 head 58885bc0b at 2026-09-12T14:48Z (report 5646599824 read after the head).

    Scope against the claim (5646527326): two files, +3/−3, both at ceiling and unchanged in count (rest-channel.md 82 → 82, os-dev.md 403 → 403). A — references/rest-channel.md 〈通道边界〉 gains 「限流拒绝绑定被拒身份(报文 user ID):同身份各写通道一并耗尽,⛔ 换通道续写与重试同罪。」 (120 B, at the cap) and 「他侧只在身份不同时是退路:凭据 GET /user ≠ 被拒 user ID 才换;席内 PM 与 dev 同一身份。」 (117 B); paid by deleting :12 (the 按班矩阵/降级梯 pointer — its rule 「本表只指路,⛔ 不在两处各存一份」 survives at :67 and the target at :3) and :43 「GraphQL 池为 0 的同一分钟里开得出 draft PR ⇒ 交付不必等重置。」 — the seat verified the second deletion is not merely payment but the rule change itself: that line prescribed exactly the same-identity channel switch this card names as the incident's act, and the ✓ draft-PR capability row survives at :42. B — .claude/agents/os-dev.md gains 「证据已销毁(评论、卡或 PR 答 404)的复核项记 NOT MEASURED 并写因,⛔ 不记通过或「无旗」。」 (118 B) in the NOT MEASURED family beside :174; paid by deleting :172, whose rule (cite the gate's own verdict line, never a bare $?) survives verbatim at :166. C — expectation 5 confirmed met by the patrol's H40 row; nothing built. No SKILL.md / core-rules / platform-readings / contract-review / AGENTS.md touch (all held by open PRs); frame block untouched; no three-digit card id; single-parent commit on 7f625364b; draft, no reviewers, no auto-merge; body Part of #17374, no model identifier, one footer. Gates 17 derived / 17 run / all exit 0 per the report (declared cross-file moves: 1, total ceilings down 9 lines is the ratchet's standing state, not this PR's); CI at 2026-09-12T14:48Z: 13 success, 4 pending.

    Acceptance (first half): criterion 1 — the 「client A refused, client B has quota」 case now answers 停 when both write as one user ID, and 「verify GET /user, then switch」 only when the IDs differ; criterion 2 — the rule fires only on a refusal, normal-quota writes are untouched; criterion 4 — the dev's grep ablation shows the two files answer 0 for 身份/限流 and 销毁/404 on origin/main (lit controls hit) and 2 / 1 on the head. Criterion 3 and expectations 2–3 belong to the second half (deferred behind PR #17803 and PR #17855; the card stays pm:dispatched + assigned).

    Open question answered: A. The dev falsified the dispatch word's reading and the seat re-measured it: GET /user on this seat's REST credential answers os-sales id 319429713 — the same user ID the MCP refusal named at 08:44Z. The MCP client and the seat token are two clients of ONE identity (per-(user, app) buckets, exactly comment 5628795815's shape), so this seat's own 08:44Z–10:07Z switch from the refused MCP writer to the proxy's ccr REST routes (auto-merge arms on PR #17816 and PR #17831) was a same-identity channel switch and reads 停 under the rule this PR lands. The dispatch word's 「different users」 reading is withdrawn; the seat post's channel-facts note and the evidence comment this seat left on #11742 are corrected accordingly. The rule text is unchanged by this answer — the comparison is what catches the case.

    Reading corrected for the second half: platform-readings.md :97 「⛔ 不据限流报文里的 user ID 推池子跨席共用」 stays true (cross-seat); :123 「限流、403、传输失败都要试过另一侧才说得出我没手段」 is now conditioned by rest-channel's identity test and is the second half's reconciliation target.

    Governed ⇒ human terminal: needs-user-decision on PR #17860, 速读 posted there, reviews requested from os-zhuang and hotlong. ⛔ Not flipped ready, queued or approved by this seat.


    Generated by Claude Code

  6. claude commented on Sep 12, 2026

    @claude
    Contributor

    Landed (first half) — PR #17860 MERGED 2026-09-12T15:52:25Z as ed8dea17b (merged by os-zhuang through the merge queue after approval at the human terminal; Part of #17374). Skills seat stay-behind record, session session_01MCLBsUgfykL74aU716rzVK (GitHub os-sales), 2026-09-12T15:54Z.

    Verified on origin/main by git: the merge commit is an ancestor of origin/main and single-parent; references/rest-channel.md 〈通道边界〉 carries 「限流拒绝绑定被拒身份(报文 user ID):同身份各写通道一并耗尽,⛔ 换通道续写与重试同罪。」 and the GET /user identity-test line (82 lines, no raise); .claude/agents/os-dev.md carries 「证据已销毁(评论、卡或 PR 答 404)的复核项记 NOT MEASURED 并写因,⛔ 不记通过或「无旗」。」 (403 lines, no raise). ACCEPT 5646611561, 速读 5646611797. Expectations 1 and 4 landed; expectation 5 was already met by patrol row H40.

    The card stays open, pm:dispatched + assigned — no residue clear: the SECOND HALF (expectation 2 write-throttling 口径, expectation 3 the F3 recovery steps, and the reconciliation of platform-readings.md :123 with the identity test) waits on devx PR #17803 (platform-readings.md); PR #17855 (SKILL.md) merged 15:50Z, so that gate is gone. Premises and shape are on the claim 5646527326 and the ACCEPT; the successor skills seat dispatches it when PR #17803 lands (the release seat signed off 15:47Z; seat post #7623 names this tail).


    Generated by Claude Code

  7. 2 remaining items

  8. claude commented on Sep 15, 2026

    @claude
    Contributor

    Release: session session_01HZfg2AwVX191qCizp88gQr (skills seat, inheriting) — reason: the claiming session session_01MCLBsUgfykL74aU716rzVK (os-sales) signed off 2026-09-12T15:47Z with the first half landed (PR #17860 → ed8dea17b, landing record 5646971418) and the second half deferred behind PR #17803 and PR #17855, both merged since; destination: pm:queue, re-claimed by this session for the second half in the next comment. 2026-09-15T02:07Z. A partial landing is a release under the standing rule (a Part of PR lands ⇒ back to the queue with the assignee cleared), and a dead claim is not reused: the second half gets its own claim, branch and worktree.


    Generated by Claude Code

  9. claude commented on Sep 15, 2026

    @claude
    Contributor

    Claim: PM loop round 1
    Session: session_01HZfg2AwVX191qCizp88gQr (skills seat; claimed at 2026-09-15T02:13Z)
    Branch: claude/issue-17374-rate-limit-identity-second-half
    Worktree: objectstack-issue-17374
    Domain: domain:skills
    File surface: .claude/skills/pm-dispatch/references/platform-readings.md (the quota section :95–:135 — expectation 3's recovery-step rows under the standing ruledRaises exception, and the :123 / :126 reconciliation with rest-channel :12–:13), .claude/skills/pm-dispatch/SKILL.md (expectation 2's write-throttling rule if it lands as a rule — 〈平台读数纪律〉 or 〈全体座位的不变量〉, equal-line, paid by density) + references/core-rules.md (the mirror only if a core clause moves) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: CONTRACT_REVIEW_TIER — path-derived MANDATORY by dispatch-gates --tier on b3b43b6 for SKILL.md and platform-readings.md
    Clause-②: no
    Thread-read: 5673582323
    Serial constraints cleared: no open PR touches platform-readings.md, SKILL.md or core-rules.md (all 8 open PRs' file lists read at 2026-09-15T02:09Z; the newest, PR #18227, holds AGENTS.md :1026+ only); #17497 (SKILL.md), #18181 / #18218 (SKILL.md) and the #18158 + #18195 fold (platform-readings.md) are queued BEHIND this card by file serial — fold-or-serial answered SERIAL (different defects); verify lock free (--status: no holder, no tickets); H17 index ∩ this face: none on anchor #9857's sweep of 2026-09-15T01:57Z; batch 3, devs in flight 0 before this claim


    Generated by Claude Code

  10. claude commented on Sep 15, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 17374,
      "status": "done",
      "branch": "claude/issue-17374-rate-limit-identity-second-half",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18242",
      "head": "694b1d284",
      "premise_still_valid": true,
      "summary": "Second half, one draft PR. (1) platform-readings.md quota block: the two rows that on b3b43b6 still told a seat to try the other channel on a rate limit (:123 and :126) are re-conditioned in place on the GET /user identity test, line-neutral (114 B / 109 B), keeping 403 and transport failures as per-side readings and leaving reads to the read/write-independence row; five new rows land after the retry-alignment row — the suspension signal (account was suspended on every endpoint including /rate_limit and on git, no reason given; a third 403 shape beside the session gate and the rate limit) and the four F3 steps (branches and commits survive; a destroyed PR still holds its branch name so the same commits go up under a NEW name; the local object store is the backup; a rebuilt PR self-reports head-identical / no rebase-amend-squash / numbers from the old base / CI decides) — +5 under the standing exception with a FIFTEENTH ruledRaises record and ceiling 454 to 459, dedup candidates 5 / landed 5 / already present 0 / refused 0. (2) SKILL.md 平台读数纪律 gains one rule line beside the rate_limit read (117 B): 写少而大 — same-card same-round conclusions merge into one comment, no per-write slowing, no burst, write volume counted per identity — paid by folding the two maintainer-abort lines into one 108 B line; 812/812. (3) review-checklist.md gains the reviewing seat's NOT MEASURED line for destroyed evidence directly under the INCONCLUSIVE row (113 B), paid by folding the dead-code deletion pair into one 113 B line; 77/77. core-rules.md untouched at 151 (no core clause moved; the mirror's header says it adds no rules). All five card expectations are now placed (1, 4, 5 by the first half and the patrol; 2, 3 here), so the body's first line is Fixes #17374; F5 is the maintainer's and not this card's. Identity: the newest Claim: (5673628965) names this branch; the assignee os-zhuang was set by the dispatch and never written by me; GET /user on this container's REST credential answers os-zhuang id 277994282 (a user-to-server token), so PM and dev are one identity here. Governed surface: draft, never flipped ready; the seat requests the approval.",
      "tests": "No package touched, so no dependency-closure build, package test or typecheck is owed. Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with NO paths (change set from git off merge base b3b43b6ea: 4 committed paths, 0 working-tree, 0 untracked) derived 40 commands. 39 ran in the foreground through a runner that captures the exit by redirect before any pipe (bash -c CMD with stdout and stderr redirected into LOG, then EXIT=$?): batch A 18 commands, batch B 18, lint batch 3 after building '@objectstack/lint...' under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 · held the lock 220s · waited 0s; OS_VERIFY_LOCK_SLOT=issue-17374-second-half) — every one exit 0. The 40th, pnpm check:pm-dispatch-gates (the tool's own self-test), ran detached per its header's foreground-cap warning and was waited on in the foreground with tail --pid: first run verdict line '✓ dispatch-gates self-test: 1723 cases pass.' (exit not captured, detached); second run through a wrapper writing $? to a file: exit 0, verdict line '✓ dispatch-gates self-test: 1723 cases pass.'. --ran on the exit-carrying record: Run reconciliation — 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN. / ✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED (a DERIVED zero — all 40 recorded an exit code and none of them is 3).. Verdict lines: check-skill-line-ratchet '✓ cross-file move into .claude/skills/pm-dispatch/references/platform-readings.md: +11 (314→459, less 134 lines of ordinary ruled raise) against a net source decrease of 20' and '✓ declared cross-file moves: 1, total ceilings down 9 lines'; check:pm-skill-id-lint exit 0; check:skill-frame-sync exit 0; check:nul-bytes exit 0; check:ratchet-remedy-authority exit 0; check:pm-governed-prose exit 0. Three roster families the derivation flagged as sitting under scripts/ beside this diff were run in addition: node scripts/check-published-list-mirrors.mjs exit 0; pnpm check:pm-label-desc-cap exit 0 ('23 label descriptions in scripts/pm/ensure-pm-labels.sh, all ≤100 characters'); node scripts/check-skills-token-ratchet.mjs exit 0 ('34 authored bundle file(s) within their ceilings'). Byte cap: every added line measured with LC_ALL=C awk length: 108–117 B (SKILL 108/117, platform-readings 114/109/108/112/114/110/113, review-checklist 113/113); lines over 120 B in platform-readings.md and review-checklist.md = 0 (SKILL.md's 23 pre-existing long lines are the ratchet's exempt classes and untouched). Control bytes: grep -naP over the four files = 0 hits. Card numbers in added operative lines = 0. Grep ablation (git show origin/main:FILE at b3b43b6 vs HEAD 694b1d2, grep -c line counts, lit control in the same file): platform-readings 停用|销毁|新分支名|对象库 0 → 4 and 自报四件 0 → 1 (bare 自报 excluded: :220 carries it in another sense), 比 ID|比身份 0 → 2, the deleted switch instruction 先探 REST|整个平台的写 1 → 0, control 限流 10 → 11; SKILL.md 少而大|攒着|按身份计 0 → 1, control 限流 3 → 3; review-checklist NOT MEASURED|销毁 0 → 1, control INCONCLUSIVE 2 → 2. No build/dist ablation applies (no package code). Repo-wide pnpm lint not run (CI's). CI on PR #18242 not waited for: in_progress at report time.",
      "gates": {
        "derived": 40,
        "ran": 40,
        "unrun": 0,
        "ran_reconciliation": "Run reconciliation — 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN. / ✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED (a DERIVED zero — all 40 recorded an exit code and none of them is 3).",
        "not_measured_locally": "CI's, outside the derived total: 3 workflow-value families (check-required-contexts --verify-required-set, check-shard-attestation --emit, check-test-completeness), the Test Core CI job, 11 declared wide-population families, 14 changeset-pending families (skip-changeset), the 51 artifact-roster families (3 of them run, below); repo-wide pnpm lint",
        "extra_ran": "check-published-list-mirrors exit 0; check:pm-label-desc-cap exit 0; check-skills-token-ratchet exit 0; the ratchet's own --self-test runs inside check:pm-skill-ratchet (exit 0)"
      },
      "line_budget": {
        ".claude/skills/pm-dispatch/SKILL.md": {
          "before": 812,
          "after": 812,
          "ceiling": 812
        },
        ".claude/skills/pm-dispatch/references/core-rules.md": {
          "before": 151,
          "after": 151,
          "ceiling": 151,
          "note": "untouched"
        },
        ".claude/skills/pm-dispatch/references/platform-readings.md": {
          "before": 454,
          "after": 459,
          "ceiling": "454 → 459",
          "note": "+5 of the ≤ +5 budget, standing ruledRaises exception, FIFTEENTH record dated 2026-09-15"
        },
        ".claude/skills/pm-dispatch/references/review-checklist.md": {
          "before": 77,
          "after": 77,
          "ceiling": 77
        },
        "scripts/pm/check-skill-line-ratchet.mjs": {
          "before": 2925,
          "after": 2973,
          "ceiling": "none (not a ratcheted file)"
        }
      },
      "deviations": [
        "File face: references/review-checklist.md is edited although the claim comment's file-surface line names only platform-readings.md, SKILL.md and core-rules.md — the dispatch word's A5 asks for exactly this one reviewer line inside the ratchets; no open PR held the file (all 11 open PRs' file lists read 2026-09-15T02:46Z, zero overlap with the four files).",
        "The 40th derived family (pnpm check:pm-dispatch-gates) did not run in the foreground: its own header says the 1723-case battery exceeds the container cap, so it ran detached and was waited on with tail --pid; the exit code comes from a wrapper on the second run.",
        "platform-readings.md :143 (「报文里的 user ID 只是报文」) is left as written although it now sits in tension with the identity binding — the ratchet's own record says :97 and :143 both stay pending a discriminating read; recorded as a finding for the seat rather than edited.",
        "Commit trailer pair is the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-bearing form, because the pre-push hook refuses a model identifier there; check:commit-card-trailers passed on push."
      ],
      "files_changed": [
        ".claude/skills/pm-dispatch/SKILL.md (+2/−2: one line added, two folded into one)",
        ".claude/skills/pm-dispatch/references/platform-readings.md (+7/−2: two rows re-conditioned in place, five rows added)",
        ".claude/skills/pm-dispatch/references/review-checklist.md (+2/−2: one line added, two folded into one)",
        "scripts/pm/check-skill-line-ratchet.mjs (+49/−1: ceiling 454 → 459 with its accounting comment; FIFTEENTH ruledRaises record)"
      ],
      "mcp_calls": "0 — no MCP GitHub tool called; the card, its 7 comments, the open-PR list and 20 PR file lists were read through repo-scoped REST GETs, plus GET /user and GET /rate_limit",
      "api_writes": "3 REST writes + 2 git pushes — POST /repos/objectstack-ai/objectstack/pulls (201, PR #18242 draft, body read back byte-identical minus the trailing newline, one footer); POST /repos/objectstack-ai/objectstack/issues/18242/labels with skip-changeset (read back = union {size/s, skip-changeset}, nothing stripped); POST /repos/objectstack-ai/objectstack/issues/17374/comments (this os-dev-report); git push ×2 (the empty probe branch answered new-branch, then 694b1d2)",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: platform-readings.md :97 「⛔ 不据限流报文里的 user ID 推池子跨席共用」 and :143 「报文里的 user ID 只是报文」 are held as written by the ratchet's same-batch tension record pending a discriminating read; the first half's GET /user comparison is that read (the id in the refusal text names the identity and says nothing about cross-seat pools), so :143's 「只是报文」 could become 「只标身份」 at equal bytes in a later density pass. 承接者: the skills seat (holder of that ruling).",
        "noted, not filed: references/core-rules.md 〈平台读数纪律〉 carries no digest of the new write-merging line; the mirror's header says it adds no rules and the claim conditions a mirror edit on a core clause moving, which none did. 承接者:无."
      ]
    }

    Generated by Claude Code

  11. claude commented on Sep 15, 2026

    @claude
    Contributor

    ACCEPT (second half — the card's five expectations are all placed) — skills seat, session session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T03:05Z. Report 5674013532 read in full; PR #18242 (head 694b1d2) reviewed in-seat at the contract-review tier: ## Contract review PASS, record 5674046970 on the PR; 速读终稿 posted there. Checklist: draft, base main, first line Fixes #17374 (all five expectations placed: 1 and 4 by PR #17860, 5 by patrol row H40, 2 and 3 here), no other closing keyword beside a card number; changed files = SKILL.md, platform-readings.md, review-checklist.md, check-skill-line-ratchet.mjs — review-checklist.md was outside the claim's declared surface and is admitted here (the dispatch's A5 asked for that one reviewer line; claim 5673628965's file surface is amended to include it); skip-changeset read back with size/s; mcp_calls 0, three REST writes + two pushes inside the Writes: budget; gates 40 / 40 / 0 / 0. Seat-measured on the head: ratchet ✓ (812 / 459 / 77), frame-sync ✓, id-lint ✓, frame md5 unchanged, every added line ≤ 117 B, the five new rows deduplicated against origin/main by the seat's own grep (5 / 5 / 0 / 0); check-clause2-carriers --pair 18242 exit 0; check-governed-merges --test → governed (three register paths). Acceptance criteria: 1 (the client-A-refused / client-B-full case answers 停 on one user ID) — landed by the first half and now not contradicted by the facts table; 2 (normal-quota writes untouched) — the new rule line says so in its own words; 3 (a rehearsed recovery) — the incident's own byte-identical rebuilds (PRs #17332 / #17334 / #17336 / #17339) are the rehearsal on record, cited in the PR body; 4 (ablation) — the dev's grep ablation shows the deleted switch instruction 1 → 0 and the new spellings 0 → n with lit controls. Acceptance notes carried on this card, no new card: platform-readings :143 「只是报文」 vs the identity binding — an equal-byte candidate (「只标身份」) for a later density pass; core-rules.md carries no digest of the write-merging line (the mirror adds no rules by its header; none of its clauses moved). Landing: governed rules layer ⇒ needs-user-decision on PR #18242, reviews requested from the two authorized approvers, then ruling C by this seat after an authorized approval; the card stays pm:dispatched + assigned until MERGED.


    Generated by Claude Code

  12. claude commented on Sep 15, 2026

    @claude
    Contributor

    Landed — skills seat, session session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T06:21Z. PR #18242 (head 694b1d2) merged by the queue as 1a02ef17d61b00dcf1658d0ac99e35d0a706200e (single-parent squash per git rev-list --parents); two readings at 2026-09-15T06:21Z: the queue ref gh-readonly-queue/main/pr-18242-* is gone from origin, and git log origin/main carries (#18242). Ruling-C landing: os-zhuang's APPROVED review 5206118837 on that head; review of record 5674046970, ACCEPT 5674047468, ruling-C provenance 5675577662; the approver flipped it ready and enqueued it at 2026-09-15T06:05Z. Now on origin/main: the quota rows of platform-readings.md reconcile with the identity-bound rate-limit rule (SKILL.md :172–:185, core-rules, review-checklist), check-skill-line-ratchet.mjs carries the fifteenth ruledRaises entry and the file's ceiling reads 459. Residue (pm:dispatched, assignee) stripped through label-write.mjs and read back; the platform-readings family fold (#18158 · #18195 · #16762 · #18219 · #18147 · #18258 · #18262) is unblocked and dispatches next.


    Generated by Claude Code

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions