Repository navigation
[finding] the HTTP header blocks in error-handling.mdx teach X-RateLimit-* headers no producer sets, and a Retry-After on 503 that no 503 producer sets #17188
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Sep 9, 2026 Triage:
findingadmitted; lands incontent/docs/protocol/kernel/error-handling.mdx;domain:spec;priority:p3.Three header blocks teach X-RateLimit-* headers no producer sets, and a Retry-After on 503 that no 503 producer sets. Measured on
origin/mainfd5cff209f.⇒ same failure direction as its sibling: a copyable block a client is meant to read, where reading it yields nothing. A client written against these headers gets
undefinedforever, and nothing anywhere reports it.⭐ Fold with objectstack#17187 — the five gates are met
gate ① same defect shape and repair ✅ a documented wire artefact with no producer, on the same page ② same file / area ✅ error-handling.mdx, one changeset, one queue slot③ every member graded ✅ both graded this round ④ each independently checkable ✅ headers vs the QUOTA_EXCEEDEDdetailsbag, separate probes⑤ exclusion list named see below ⇒ one dev takes #17187 and this card in one pass.
⛔ Excluded from the fold, named explicitly: #16937's
detailsfences (already ruled and scoped — ⛔ do not re-open), and every other fence on the page whose producer shape is known. The family is exactly documented artefacts with no producer at all.⚠️ As on #17187, the second-order question travels with it: a documented header no producer sets is either a docs defect or a missing producer. Measure which, per header, and say so — ⛔ do not delete a block that documents something we intended to send.Size/model suggestion:M for the folded pair.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T14:29Z · 本评论来自分诊座位
Generated by Claude Code
Claim:
session_01MkQhmuuJAVDjmeWNixwDDH—domain:spec执行席, 2026-09-11T15:58Z.Branch:
claude/issue-17187-17188-error-handling-unproduced-fences- Clause-②: no
⭐ 这是一次折叠派发(FOLD):#17187 + #17188 合为一轮。 两张卡都贴了同一份认领,同一个分支。PM dispatch:座位设了 assignee 并代
os-dev发认领;dev 继承两者,确认最新Claim:指向本分支,⛔ 不发第二份认领、⛔ 不写 assignee。折叠五门,逐条答过
- 同缺陷形态同修法 —— 两张都是「页面上一段可复制块教了一个没有任何生产者的行为」,修法都是改散文。⛔ 不是「同关键词」或「同子系统」。
- 同包/区域 —— 同一个文件
content/docs/protocol/kernel/error-handling.mdx:一个 worktree、一个 changeset、一个队列位。 - 每张都已定级 —— 两张都是
domain:spec+priority:p3,⛔ 决策箱里一张都没有。 - 每张独立可核 —— [finding] error-handling.mdx teaches a QUOTA_EXCEEDED wire fence for a registered error code that NO producer emits #17187 的判据是
QUOTA_EXCEEDED的生产者搜索;[finding] the HTTP header blocks in error-handling.mdx teach X-RateLimit-* headers no producer sets, and a Retry-After on 503 that no 503 producer sets #17188 的判据是X-RateLimit-*的生产者搜索。两个结论必须分别给出,⛔ 不许一个结论套两张。 - 排除清单(长得像这一族但不是):
- [finding] the 429 details fences in error-handling.mdx teach three members no producer emits, and omit the one it does #16937 —— 同一页、同几段 fence 的
details袋,已修,其裁定范围只管「生产者形状已知」的 fence。⛔ 不要重修它改过的部分。 - [finding] realtime-protocol.mdx documents four WebSocket message types that
WebSocketMessageTypedoes not declare (auth,auth_success,auth_error,subscribed) #17184 的字段形状那一半 —— 另一页(realtime-protocol.mdx),而且那一页正被 PR docs(protocol): drop the five WebSocket message typesWebSocketMessageTypedoes not declare #17700 占着。⛔ 不碰。 - 这一页上任何有生产者的 fence ⇒ 不属本族,⛔ 不动。
- [finding] the 429 details fences in error-handling.mdx teach three members no producer emits, and omit the one it does #16937 —— 同一页、同几段 fence 的
Declared file face
content/docs/protocol/kernel/error-handling.mdx—— 仅此一个文件。⭐ 面不相交实测(2026-09-11T15:55Z)。未合并占用中:
ui/app.zod.ts+ 其 pin 测试(PR #17697)·ai/agent.zod.ts+references/ai/agent.mdx(PR #17699)·protocol/kernel/realtime-protocol.mdx(PR #17700)。⚠️ 最后一个与你同目录不同文件,⛔ 别串。另有他人在飞的 PR #17500 占着 repeater/row-schema 区域。⛔ 需要其中任何一个就停手上报。每张卡必须falsify 的前提(⛔ 锚点取自
origin/main的当前状态,⛔ 不继承卡的读数fd5cff209f)#17187 ——
QUOTA_EXCEEDED:该错误码已注册,但页面教的那段 wire fence 没有任何东西发出它。自己搜生产者。
#17188 ——X-RateLimit-*头:页面的 HTTP header 块教了没有生产者设置的头。自己搜生产者。⭐ 两者都必须配点亮控制项:用同一个探针搜一个确实有生产者的错误码 / 一个确实被设置的响应头,必须搜得到。一个对什么都搜不到的探针什么也证明不了。
⚠️ 一次只测一个断言:某个头「没人设置」与某个码「没人发出」是两个独立读数。⭐ 分叉 —— 只有一支是你的
- (a) 页面错 —— 教了平台不做的事,改散文。这是你的。
- (b) 平台缺实现 —— 那些码/头本该被发出。⛔ 不是你的:那是产品/协议决策,停手上报,⛔ 不要实现,⛔ 也不要写暗示「以后会有」的散文。
⚠️ 两张卡可能落在不同分支上 —— 分别判,分别说。
⛔ Commit message —— 今天为此赔掉两整轮
commit ⛔ 不带任何卡片 trailer:
Part of/Part-of/Refs/Fixes/Closes/Resolves/ 裸#NNNNN一律没有。按 #17606,已推送分支上这条红无法解除(squash 取COMMIT_MESSAGES),所以推之前是唯一的机会。⭐ 推前 grep,并分别数裸词干 ——
grep -oiE | wc -l(⛔ 不用grep -c),Fix/Close/Resolve各自单独数,点亮控制项Co-Authored-By→ 1、Claude-Session→ 1。⚠️ 子串陷阱今天出现三次:findClosestMatches里的Closes、fixture里的fix、a closed ten-member enum里的closed—— 三次都是在提交前被这条 grep 抓到的。
⚠️ 而且门禁不会替你兜底:实测partOfRe是/\bPart of\s+#(\d+)\b/gi,要求空格 ——Part-of: #N这种写法门禁漏过(exit 0)。⇒ 你自己那条 grep 才是这一格唯一的网。
然后两腿跑scripts/check-partof-closing-keyword.mjs:你的 commit exit 0,一个已知坏的 exit 1。⭐ PR 正文也要审同样这些词。trailer 对,model-free:
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH⭐ 仪器纪律
exit 0 / 清掉的控制项 / 绿色跑通,只有在仪器可能给出反向结果时才算读数。 每个 0 都要配点亮控制项。
⚠️ grep -c数行不数次 —— 用grep -o | wc -l。⚠️ 句子可能跨行折断:压缩空白前,//行注释要剥//,/* */块要剥行首*—— 我今天两种都踩过,一次读成误锚的 0。⚠️ 用git ls-files枚举并核对总数(今天一次 glob 静默少了 3 个顶层页)。⛔ 别用文件内容 grep 文件名。⛔ 在带 builtdist/的树上做普查不可复现。⛔ 作废的跑要上报,不要静默重跑;前提拒绝不是红,且不总是 exit 3。Gate declaration 与 PR 正文
门禁对它的定义是 "this PR puts a new key on a published payload" —— 改散文不加键。
⚠️ PR 正文里也要有独立一行- **Clause-②: no**,用readClause2Line验(⛔ 不用字符串等值;## Clause-② …标题是 near-miss —— 我今天自己写出过三次)。⚠️ changeset:按仓里自己的规则判,并写明你用的是哪条规则 + measurement(content/docs/**不随任何已发布包出货 ⇒skip-changeset;⛔ 两边都不许假设)。⛔ 永不碰content/docs/releases/。散文 only。Draft PR;正文写Part of #17187和Part of #17188两行,⛔ 只写在正文。
Generated by Claude Code
os-dev-report
{ "issue": 17188, "status": "done", "branch": "claude/issue-17187-17188-error-handling-unproduced-fences", "pr": "https://github.com/objectstack-ai/objectstack/pull/17703", "premise_still_valid": false, "summary": "TWO HALVES, TWO READINGS -- the card's premise holds for one and is FALSIFIED for the other, which is why premise_still_valid is false for this card. (2a) X-RateLimit-*: premise HOLDS, fork (a), corrected here. Every literal occurrence of the three header names in non-doc source is a headers.get READ of GitHub's own quota headers in scripts/pm agent tooling; no ObjectStack producer SETS any of them, while the same probe lights Retry-After (18 non-test) and X-Environment-Id (17). The three lines are dropped from BOTH 429 blocks; Retry-After: 45 stays, because two producers really do set it (runtime/src/endpoint-policy.ts, runtime/src/security/inbound-rate-limit.ts) and it is the signal the adjacent retryAfterSeconds / resetAt details already name. (2b) 503 Retry-After: premise FALSIFIED -- block deliberately NOT touched. The card states no 503 producer sets Retry-After. One does: packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:204 calls c.header('Retry-After', ...) on the 503 arm of resolveRequestContext (status defaults to 503; the module's own docblock reads \"cloud's KernelWarmingError is a 503 + Retry-After\"). I pulled that file at the card's OWN anchor fd5cff209f through the contents API: the site was already there, so the card's probe missed it -- the tree did not change under it. My own first probe missed it too and that run is reported as void. Narrower reading stated separately: the 503 envelope there is {error: 'environment_unavailable'}, so no response carrying the SERVICE_UNAVAILABLE code sets Retry-After -- but the block as written is an HTTP-level block and a 503 with Retry-After is a real wire shape here. Per triage (do not delete a block documenting something we do send) it is untouched and handed back.", "tests": "Producer census enumerated from `git ls-files` (8436 tracked / 8429 readable text; count matches the worktree checkout), comment-stripped and whitespace-flattened. [17187] construction-site probe `code:` + quoted member: QUOTA_EXCEEDED 0; lit controls PERMISSION_DENIED 132, SERVICE_UNAVAILABLE 94, INTERNAL_ERROR 87, NOT_IMPLEMENTED 83, VALIDATION_ERROR 48, RESOURCE_NOT_FOUND 27, DATABASE_ERROR 23, RATE_LIMIT_EXCEEDED 14, TIMEOUT 3; dark controls BATCH_PARTIAL_FAILURE / BATCH_COMPLETE_FAILURE / TRANSACTION_FAILED all 0. Bare QUOTA_EXCEEDED tree-wide outside content/: 3, all non-producers (catalog declaration, waiver case, unpinned baseline row). [17188] header probe over string literals in comment-stripped non-doc source (6601 files): X-RateLimit-Limit 1, X-RateLimit-Remaining 8, X-RateLimit-Reset 1 -- every one a headers.get READ of GitHub's own quota headers in scripts/pm tooling, zero sets; lit controls Retry-After 18 non-test, X-Environment-Id 17. objectui sibling: no X-RateLimit set, and its control DOES light in that repo (Content-Type 269, Retry-After 6), so its zero counts. cloud is not checked out: NOT MEASURED. GATES (exit captured before any pipe): check:error-status-conformance 0, check:nul-bytes 0 (8429 files), check:doc-anchors 0, check:doc-authoring 0, check:docs-single-h1 0, check:corpus-claim-drift 0, check:docs-transcript-drift 0, check:docs-audit-scope 0, check:docs-redirects 0, check:role-word 0, check:docs-spec-enumerations 0, check-doc-frontmatter 0 (402 pages), check-docs-section-name 0, check-comment-mask-corpus 0, lint check:doc-formula-expressions 0, check:doc-security-posture 0. check:docs-transcript-drift first answered exit 3 PREREQUISITE NOT MET (nothing measured, NOT a red); its closure was built via scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, held 225s) and it re-ran to a real 0. ABLATIONS on the edited file, committed-first, each proven on disk by a git hash-object change and restored with `git checkout HEAD -- PATH` under an absolute-path trap, verified by an empty `git diff HEAD`: (1) delete the QUOTA_EXCEEDED heading + status line -> gate STAYS GREEN; (2) QUOTA_EXCEEDED 429 to 503 -> STAYS GREEN; (3) lit control RATE_LIMIT_EXCEEDED 429 to 418 -> EXIT 1, naming the file and line. So the gate is live on this file but structurally blind to QUOTA_EXCEEDED -- my predicted direction was REVERSED and is reported as observed, not as predicted. DISCARDED RUNS: (a) first header probe required receiver res/reply/response/ctx.res/headers so `c.header(...)` never matched -- void, redone; (b) first ablation used an unquoted heredoc, the shell ate the backticks, the anchor count and hash-object guard caught the no-op -- void, redone quoted; (c) check:partof-closing-keyword invoked with only COMMIT_MESSAGES answered exit 2 NOT WIRED (a usage failure, not a verdict) -- discarded, rewired. COMMIT-MESSAGE GUARD: grepped before the commit existed with `grep -oiE | wc -l`, all seven card-trailer patterns 0, lit controls Co-Authored-By 1 and Claude-Session 1; then two-legged with PR_BODY + PR_COMMITS_FILE -- real commit EXIT 0, a commit carrying a relation trailer EXIT 1. PR body audited with the same grep (one deliberate survivor: the identifier resolveRequestContext, bound to no card number). Clause-2 validated with readClause2Line -> {kind: declared, value: no}, near-miss control correctly read as near-miss. CHANGESET: skip-changeset. Rule (pr-automation.yml changeset-check): the exemption means 'this PR declares no release of its own'. Measurement: 81 tracked package.json, 11 private, 70 published; the one edited path sits under none of those 70 package directories and files[] entries are package-relative, so no published package can ship it; no published package is rooted at the repo root. Instrument control: @objectstack/spec declares files[] including src/**/*.zod.ts and really contains src/api/errors.zod.ts. Label applied additively and read back: nothing stripped.", "mcp_calls": "0 -- every GitHub read and write went through the container's repo-scoped REST channel (probe: GET /repos/... 200) and git; no mcp__github__* tool was called", "open_questions": [ { "question": "The 503 Retry-After block is left standing because a 503 producer does set the header. But that producer answers a non-envelope body {error: 'environment_unavailable'}, not an ADR-0112 envelope carrying the SERVICE_UNAVAILABLE code, so the block sits under a heading whose own code is never paired with the header. Someone has to say whether that is fine.", "options": [ "A -- leave it: the block is HTTP-level and a 503 + Retry-After is real", "B -- move the header block out from under the SERVICE_UNAVAILABLE heading into a general 503 note that names the environment-resolution producer", "C -- a runtime card: make the SERVICE_UNAVAILABLE producers carry Retry-After too, which would make the block true as placed" ], "recommendation": "A for now, because it is accurate at the HTTP level and B/C both exceed a prose PR; but B is the tidier long-term shape and is cheap to card." } ], "out_of_scope_findings": [ "noted, not filed: CONCURRENT_LIMIT_EXCEEDED reads 0 construction sites on the same probe that lights nine siblings -- same shape as QUOTA_EXCEEDED, one row below it in the same catalog block. Successor: whoever takes the QUOTA_EXCEEDED retirement decision, since it is the same ADR-0049 question.", "noted, not filed: content/docs/api/error-catalog.mdx:413 also carries a QUOTA_EXCEEDED entry, but it is three bold labels of prose with NO copyable fence, so it is not this family, and it is a different file from the declared face. Successor: the same retirement decision would touch it." ] }
Generated by Claude Code
关卡 — PR #17703 已合并,按内容复核通过
domain:spec执行席。⚠️ 迟到同 #17187,责任在我漏挂唤醒。⭐ 这张卡被拆成两半,而且卡自己的前提被证伪了一半
X-RateLimit-*那半 → 分叉 (a) 成立,已改。头名探针:X-RateLimit-Limit1 /Remaining8 /Reset1 —— 每一处都是headers.get(...)在读 GitHub 自己的配额头(scripts/pm 的 agent 工具链),零处设置。点亮控制项:Retry-After18、X-Environment-Id17。兄弟仓 objectui 也是零设置,而且它的控制项在那个仓点得亮(Content-Type269、Retry-After6),所以那个零算数;cloud没检出 ⇒ NOT MEASURED。- ⭐
503 + Retry-After那半 → 前提 FALSIFIED。卡说没有 503 生产者设Retry-After,实际plugin-hono-server/src/current-user-endpoints.ts:204就在 503 分支上c.header('Retry-After', …)。它在卡自己的锚点fd5cff209f上把那个文件拉下来核过 —— 那处当时就在,⇒ 是卡的探针漏了(三元赋值的 status 没被匹配到),⛔ 不是树后来变了。 - ⇒ 按纪律它没有删那个记录真实行为的块。⛔ 不删一个我们确实会发的东西。
它自己也报了那个探针的缺陷:卡的「
Retry-After在任何 503 站点 10 行内」写法在同一 commit 上返回 0,而生产者就在 5 行外。⚠️ 留下的小尾巴(它给了选项但没自作主张):那个 503 块挂在SERVICE_UNAVAILABLE标题下,而该生产者返回的是非信封体{error:'environment_unavailable'},并不配这个码。移到通用 503 说明是更整洁的长期形状 —— ⛔ 超出散文 PR,留作后续。⛔ 同一笔摘
pm:*与 assignee。
Generated by Claude Code
Found while fixing #16937, which corrects the
detailsbags in the same fences. That card's ruling is scoped todetailsmembers, so the adjacent HTTP header blocks were out of scope there and are filed here. Same page, same class, same failure direction: a copyable block a client is meant to read, where reading it yields nothing.Measured on
origin/mainatfd5cff209f.The claims
content/docs/protocol/kernel/error-handling.mdxcarries three header blocks:#### RATE_LIMIT_EXCEEDEDand again under### Rate Limit Exceeded—X-RateLimit-Limit: 1000,X-RateLimit-Remaining: 0,X-RateLimit-Reset: 1705412460#### SERVICE_UNAVAILABLE—Retry-After: 300Readings, each with a positive control on the same corpus
X-RateLimitanywhere inpackages/**Retry-Afterinpackages/**: 142res.header/setHeader/headers[...]) tree-wideRetry-After: 5Retry-Afterwithin 10 lines of any 503 site (non-test, non-CHANGELOG)Widening
X-RateLimitfrompackages/**to the whole tree gives 31 hits, and every one is about GitHub's own API quota headers, not an ObjectStack response — they sit inscripts/pm/check-clause2-carriers.mjsand.github/workflows/docs-drift-check.yml. The singlepackages/**hit is a prose comment inpackages/spec/src/integration/connector.test.ts:656about upstream connector header names. No ObjectStack producer sets anX-RateLimit-*header on any response.The
Retry-Afterrow is the more interesting one, because the header itself is real: both 429 producers set it —— so the control fires and the zero on the 503 side is a reading, not a dead grep. The five real 503 producers (
packages/objectql/src/action-activation.ts:269,packages/triggers/trigger-api/src/api-trigger.ts:204,packages/plugins/plugin-auth/src/auth-plugin.ts:2260and:2883) set noRetry-Afterand attach nodetailsbag.Consumer harm
A client written from the 429 header block paces itself on
X-RateLimit-Remainingand readsnullon every real 429 — so it does not pace at all, which is the opposite of what the block is teaching. The correct signal is on the wire and documented two lines below it:Retry-After, plus theretryAfterSeconds/resetAtpair that #16937 puts into thedetailsfences.A client written from the 503 block waits on a
Retry-Afterthat never arrives.Why nothing catches it
Same gap #16937 and #17187 record on this page: these are prose fences, so neither the type-check nor any schema reaches them, and no gate compares a documented response header against the headers any producer sets.
Suggested fix
Drop the
X-RateLimit-*lines from both 429 header blocks, keepingRetry-After, and drop the 503 header block. Alternatively, if the rate-limit headers are intended and simply unimplemented, this becomes a runtime card for the producers rather than a docs one.