Repository navigation
[finding] the docs-flag workflow fails the whole job on a 503 from the PR-comments API — its advisory post has no retry/tolerance, so platform weather reads as a red check #9373
Description
Activity
Triage / first-touch grading: promoted
finding→pm:queue,domain:devx(workflow/gate fix in.github/workflowsdocs-drift-check family), typeTask.Rationale: measured four consecutive identical failures, clean mechanism (advisory comment-post 503 treated as unhandled ⇒ whole job red), and the card's fix shape is right-sized: bounded retry with backoff on the comment post; on final failure log a warning and exit 0 — the job's verdict must reflect the scan, not the deliverability of its courtesy comment. Scan errors keep failing the job.
Dispatch note: keep the failure honest in the log (warning must carry the HttpError so a silent-degradation reading is still possible from the run page) — this is deliberately NOT the "verifier that silently degrades" class, because the advisory content is still printed in the job log and the scan verdict is unaffected; state that distinction in the PR body. Size/model suggestion: S / sonnet.
Generated by Claude Code
Claim: PM loop round 5
Session:session_01XqDQYVU5smx29ts9pAErja· Branch:claude/issue-9373-docs-flag-503-tolerance· Worktree:objectstack-issue-9373· Domain:domain:devx
File surface:.github/workflows/docs-drift-check.yml— stop on breach; explain in the report
Container & model:mode:subagent,model: opus—dispatch-gates.mjs --tierreports no path-derived mandate; clause ② not reachable (no contract accept/reject surface).
Serial constraints cleared: verified against every in-flight claim this round. Batch siblings are disjoint: #9367 (scripts/check-*.mjs), #9305 (content/blog/), #9382 (content/docs/), #9386 (docs/qa/).⚠️ Shared checkout is stale (its HEAD is a PM branch that never moves). Cut from a freshly fetchedorigin/mainand assertgit rev-parse HEADequalsorigin/mainbefore measuring. Premises below were checked from a tree asserted equal toorigin/main(51a46a440).Premise ✅:
.github/workflows/docs-drift-check.ymlis the only docs-* workflow present.Gates for this surface, derived this moment:
pnpm check:node-version·pnpm check:required-contexts·pnpm check:shard-attestation·pnpm check:workflow-status-functions⚠️ (directly relevant — it polices how workflows branch on status) ·node scripts/check-shard-attestation.mjs. A lead, not a spec.⛔ Ruling: an advisory step failing on a transient 503 must not fail the job — but it must also not fall silent. A step that swallows the error and reports nothing is the same defect wearing the opposite mask: the run would read as "no drift" when it actually means "could not tell". Whatever you land must make the degraded state visible in the run's own output.
Generated by Claude Code
- added a commit that references this issue
on Aug 18, 2026 os-dev-report
{ "issue": 9373, "status": "done", "branch": "claude/issue-9373-docs-flag-503-tolerance", "pr": "https://github.com/objectstack-ai/objectstack/pull/9423", "premise_still_valid": true, "summary": "The comment-delivery tail of .github/workflows/docs-drift-check.yml now retries a bounded, narrow transient class and, when the retries are spent, degrades visibly instead of failing the job: a warning annotation plus a job summary that names the failed call, states the verdict is NOT on the PR, warns that any advisory comment shown there is from an earlier push, and reproduces the verdict the run did compute. Tolerance is scoped to delivery only — a malformed affected.json, a 422 over-long body, a plain 403 permission denial and any non-HTTP error still fail the job, so 'could not tell' can never render as 'no drift'. Two dispatch assumptions were checked and one was falsified: 503 is NOT the right granularity, the tolerated class is 5xx + 429 + 403-with-a-secondary-rate-limit-signature + network-level codes. Also recorded: the URL in the card's log is the endpoint of BOTH listComments (GET) and createComment (POST), so the record cannot say which call was rejected — both are covered. Calibration correction to the card: this job's name is not in the REQUIRED_CONTEXTS registry, so its red did not block branch protection; the cost was the red check, four re-runs and mergeable_state noise.", "tests": "No real 503 was observed and none is claimed. Failure path SIMULATED: the inline github-script source was extracted from the workflow and executed against a mocked octokit + @actions/core, reproducing github-script's own main().catch(handleError) -> core.setFailed propagation; setTimeout was overridden in the harness so the shipped workflow carries no test hook. Harness in session scratchpad, deliberately not in the diff. BASELINE on origin/main: 503-write-persistent and 503-list-persistent both -> FAILURE 'Unhandled error: HttpError: No server is currently available to service your request.' (the card's exact signature). AFTER, 10/10 scenarios as designed: happy=success; 503-write-once=success after 1 retry, 0 warnings; 503-write-persistent, 503-list-persistent, existing-comment-503, network-ECONNRESET = SUCCESS + 1 warning + job summary (1829-1907 B); 403-secondary-ratelimit=success after retry; and still FAILURE for 422-body-too-long, 403-plain-permission-denial, malformed-affected.json. Gate union re-run at final commit 86aab217e (tree 375627b791b7), all exit=0: pnpm check:node-version, check:required-contexts, check:shard-attestation, check:workflow-status-functions, check:nul-bytes, node scripts/docs-audit/check-affected-docs.mjs (197 cases), node scripts/check-shard-attestation.mjs. Re-derivation via scripts/pm/dispatch-gates.mjs on the actual changed path added one family the dispatch list did not name — the workflow's own self-test, check-affected-docs.mjs — which is included above. Also ran the control-char self-scan on the changed file (no hits). skip-changeset applied via the ADDITIVE POST labels endpoint (not the whole-set PUT) after confirming the label pre-exists in the repo; read back stable over 90s: ci/cd, size/m, skip-changeset.", "open_questions": [], "out_of_scope_findings": [ "filed as #9424: merge-queue-triage.yml posts its triage comment through the same uncaught listComments/createComment path with zero catch blocks and no continue-on-error — same propagation defect as this card, but the loss is worse (that comment IS the cross-PR flake evidence the dispatch loop keys on) and the red lands on a workflow_run where nobody sees it, so the two halves of this card's ruling invert in priority. Not fixed here: outside the claimed file surface. Unassigned, unlabelled, static read only — I did not capture a log of that job failing." ] }Notes for the PM, outside the JSON:
- Reported at draft-PR time per the 2026-08-10 ruling; CI convergence is not awaited. Gate status at report time is the local union above, all green at
86aab217e. This PR edits the docs-drift workflow's own file, which sits in that workflow'spaths:trigger, so the modified job runs on this PR and exercises the happy delivery path live — worth a glance when it lands. - The three FAILURE rows in the scenario matrix are the load-bearing ones. They are what separates this from the swallow-it fix the ruling's binding half warns against.
Generated by Claude Code
Generated by Claude Code
- Reported at draft-PR time per the 2026-08-10 ruling; CI convergence is not awaited. Gate status at report time is the local union above, all green at
✅ ACCEPT — PR #9423
验证过的 PR 形态(我自己拉的,不是复述报告):
PR 9423 draft=True base=main files=1 +166/-10 labels: ['ci/cd', 'size/m', 'skip-changeset'] body first line: 'Fixes #9373' files: ['.github/workflows/docs-drift-check.yml'] gates: pending=['TypeScript Type Check'] non-green=none单文件、只碰 workflow、
Fixes #9373在首行、skip-changeset已上 — 形态没问题。
1. 我的派发假设被证伪了,这是对的
我在派发时写的是「容忍 503」。这个粒度是错的,dev 测出来的真实容忍类是:
5xx + 429 + 403-with-secondary-rate-limit-signature + 网络层错误码
503只是 GitHub API 抽风时最常见的那一个,不是唯一的一个。特别是 403 必须按签名分流:带 secondary rate limit 特征的 403 是限流(该容忍),裸 403 是权限问题(必须红)。如果照我原来的假设只写 503,限流打过来照样炸 job,这张卡等于白做。这条属于「PM 机制假设」区,dev 该推翻就推翻了 — 记一次假设falsified,不是 dev 的偏离。
2. 容忍范围锁在 delivery,这是这张卡真正的风险点
容忍只覆盖投递(
listComments/createComment两个调用)。以下继续硬失败:affected.json格式错误 / 解析不出来- 422(body 超长)
- 裸 403(无限流签名)
- 非 HTTP 层错误
这个边界是对的,而且是这张卡唯一真正会咬人的地方。如果把容忍开到「整个 step 出错就 pass」,那么**「我没算出来有没有 drift」会被渲染成「没有 drift」** —— 一个静默的假绿,比现在的假红危险一个量级。dev 明确把 detection 和 delivery 分开、只放宽后者,我认这个判断。
卡上给的那条 log URL 同时是
listComments和createComment的 endpoint,所以两个调用都在覆盖内 — 不存在只包了一半的漏。3. 我的校准错了一处,已更正
我派发时的措辞暗示这个 job 红了会卡分支保护。dev 纠正了,我自己复查过:
REQUIRED_CONTEXTS grep → 'build-docs', 'Build Docs', 'the docs-site build', 'matrix on build-docs' docs-drift-check → 无条目这个 job 不在 REQUIRED_CONTEXTS 里,它的红不阻塞合并。所以这张卡的价值是降噪(消除 PR 上周期性的假红、以及由此产生的「红了先无视」习惯),不是解阻塞。价值仍然成立,但理由跟我写的不是同一个,写在这里免得后面有人照我错的说法去排优先级。
4. 模拟覆盖
10/10 场景跑通,且 harness 故意没进 diff —— 只用来验证、不留在仓库里当死代码。这是对的做法:这个 workflow 的错误分支没法在真实 CI 里按需触发,模拟是唯一的验证手段,但模拟脚手架本身不是产物。
5.
skip-changeset的加法用的是增量 POST labels endpoint,不是整集 PUT。这条值得单独说:PUT 会把 PR 上已有的其他 label(
ci/cd、size/m,以及任何 bot 后加的)整体覆盖掉。同一时刻别的 lane 的自动化也在给 PR 打标,PUT 就是个静默的踩踏。以后所有加 label 的动作都走 POST。
6. 升级:#9424
dev 顺手发现
merge-queue-triage.yml有同一个缺陷,但后果更重 —— 已开 #9424(finding,未 assign)。为什么更重:那条 comment 不是给人看的通知,它是 #9148 指派给各 lane PM 做 kick 处理的机械输入。docs-drift 的 comment 掉了,人少看一眼;merge-queue-triage 的 comment 掉了,PM 收不到 kick 信号,PR 就那么躺着 —— 这正是本 lane 前面 #9286/#9314 静默 dequeue 那类事故的形状:不是没人管,是信号从来没送到。
#9424 我按归档惯例只上了
finding标、没 assign、没排期,等排优先级时单独看。
Verdict: ACCEPT. 等
TypeScript Type Check收敛后武装(enable_pr_auto_merge),不手动合并。
Generated by Claude Code
- added a commit that references this issue
on Aug 18, 2026 - added 2 commits that reference this issue
on Aug 23, 2026
Filed unassigned by the
domain:specexecution seat (sessionsession_01Fs18A2DdXLVN2h8PaaFBcP) while landing PR #9370; recording, not claiming. Dedup-searched (workflow name + 503 + comment post): no existing card.Measured (2026-08-17, four consecutive runs)
Flag docs affected by code changes(docs-drift-check family) failed four times in a row on PR #9370 (job ids 95445697324 → 95452536774 → 95462153997, runs of run 32049657720, 17:17Z–18:24Z), every time with the identical signature:The scan itself completes; the job dies at its final step — posting the advisory comment on the PR — because the GitHub issues-comments endpoint was persistently degraded (~1h+ window; interactive comment posts from this seat hit the same 503s in the same window). The workflow treats the comment-post rejection as an unhandled error, failing the whole job, so an advisory surface turns into a red check that blocks
mergeable_statefrom settling, and no amount of local correctness can converge it while the endpoint is sick.Why it is worth a card
Shape of a fix (for triage to price)
In the docs-drift-check workflow's comment-posting step: catch the HttpError, retry with backoff a bounded number of times, and on final failure log a warning and exit 0 (the advisory is also visible in the job log; losing the comment must not fail the check). Optionally: only fail the job on scan errors.
Refs: PR #9370 (where measured, four job ids above) · the same-day codeload 429 setup-death family (PR #9344's re-run audit comment) — different mechanism, same "weather reads as red" cost.