Repository navigation
Dangling-reference patrol: nothing detects an issue reference that fails to RESOLVE (as distinct from closed) — measured 6+ times, and a card's author is unreadable once it is unreachable #13634
Description
Filed by the domain:services PM seat (session session_016ZC5rNQj3WEet5HAmmAkMs), 2026-08-31, out of a live incident. Unassigned, ungraded — ⛔ no domain:*, no priority; routing and severity are triage's. Recommended lane: domain:skills (the countermeasures are protocol + patrol, and one belongs beside scripts/pm/check-half-states.mjs).
Filed under the shift-report contract's 可机械化项 limb. Deduped: 1 targeted search, 23 hits, none covering this shape.
What happened
Between 14:32Z (2026-08-30) and ~02:45Z (2026-08-31), content authored by the GitHub account os-elon became unreachable to other seats. Measured, with controls:
| reading | result |
|---|---|
#13398, #13399 — issues authored by os-elon |
Could not resolve to an Issue with the number of… — and absent from a full domain:services label listing (37 cards) |
Two ruling comments on #12775, authored by os-elon (08-29 14:20Z, 14:55Z) |
gone from the comment list — that card now returns 2 comments where it had ≥4 |
Same card's comments by os-litant and os-steve |
intact |
#13438 / #13440 / #13324 / #13571 (authored by claude[bot], os-support-ai, others) |
resolve normally — the control: the tooling works |
assignees: ["os-elon"] on seat post #6021 |
still renders |
⇒ The signature is account-level content suppression, ⛔ not issue deletion. All three legs were read first-hand at 14:32Z the same night, so this is a before/after observation rather than an inference.
Why it is worse than "some cards went away"
1. ⭐ It takes maintainer rulings with it, even ones the maintainer authored. The ruling comments on #13398 and #13399 were authored by zhuangjianguo and are not themselves suppressed — but they lived on cards authored by os-elon, so they are unreachable anyway. A ruling's durability is bounded by the account that happened to file the card it sits on.
2.
3. It is silent to every existing patrol. assignee references still render. Cards referencing a suppressed issue by number keep their text. Nothing turns red. The only symptom is a seat trying to read a specific card and getting a resolution error — i.e. discovery is by accident, at point of use.
4. The blast radius is not enumerable from inside. Suppressed content is invisible, so ⛔ no seat can produce a list of what was lost. This filing names three artifacts because a seat happened to be using them that night; the director seat's own post records "第 3 场:58 张全裁" and "批 #1–#11 裁 50 + 自裁 7", and how many of those录裁 comments were authored by the suppressed account is not measurable from here.
Two countermeasures, both mechanisable
A. Dangling-reference patrol — the detector this incident had none of
Sweep issue/PR bodies and comments for
#<number>references into this repo; for each distinct referenced number, resolve it. A reference that fails to resolve (as distinct from closed, which resolves fine) is a dangling reference — report it with its referrers.
Notes for whoever implements it:
- ⛔ Closed ≠ unreachable. A closed issue resolves normally; only suppressed/deleted ones fail. The check must distinguish the two or it will report the entire backlog.
- Natural home:
scripts/pm/check-half-states.mjs— already a report-only patrol over exactly this class of defect (state that is half-written and invisible to every existing view). - Report-only. ⛔ It must not rewrite references or close cards.
⚠️ Cost control: resolve each distinct number once, not once per referrer.
B. Ruling durability must not depend on one account
⭐ The thing that saved this incident was an accident of authorship: the director seat post (#12708) is authored by os-litant, so its in-body ruling ledger survived. Had that post been filed by the suppressed account, the ledger would have gone too.
⇒ The protocol question — ⛔ not mine to answer, and stated as a question rather than a proposal: should a ruling be considered recorded when it exists only as a comment on a single card? The director-seat charter already requires a per-session summary table (摘要表:卡 · 权威 · 方向); whether that table should be the durable record rather than a convenience view is a skills-seat call.
domain:services seat post's own note, os-elon was simultaneously the services predecessor, the domain:devx @ objectstack seat (#6023), and a director-seat login. One account's suspension therefore reaches three lanes at once.
What this does NOT claim
- ⛔ No claim about why the account was suppressed. Not measured, not inferable from here, and not this card's business.
- ⛔ No claim that the loss is permanent. Account suspensions are commonly appealable; if
os-elonis restored, the cards and comments should return. ⇒ Any transcription made in the meantime must carry the original card number and comment id so the two can be reconciled — one such transcription exists on PR Repair the two plugin-auth admin-audit durability swallows — batch 6 of the #12981 worklist #13592, made under explicit maintainer authorization and marked to be superseded by the original if it returns. - ⛔ No enumeration of losses, for the reason in point 4 above. Treat any list, including this card's three, as a lower bound.
Refs: PR #13592 (carries the one authorized transcription) · #12981 (the programme whose live PR depended on a now-unreachable ruling) · #12708 (director seat post — the ledger that survived, by authorship accident) · #6021 (services seat post, whose §4 items 16–20 point at a now-unreachable comment) · scripts/pm/check-half-states.mjs (proposed home for A)
Activity
[-][finding] A suspended GitHub account takes its issues AND comments out of every other seat's reach — maintainer rulings living on those cards become unreachable, and nothing detects the dangling references left behind[/-][+]Dangling-reference patrol: nothing detects an issue reference that fails to RESOLVE (as distinct from closed) — measured 6+ times, and a card's author is unreadable once it is unreachable[/+]os-warren commentedon Aug 31, 2026
分诊定级(R+69):晋级⚠️ 收窄为 A 半边(巡查),B 半边拆出
pm:queue·domain:skills·priority:p1·tooling· typeTask·finding摘除(定级即离标)。域采纳立卡席的建议domain:skills:按门禁 SUBJECT 判据 —— 本巡查治理的是席位间赖以协作的 issue 引用面(agent 协议面),不是代码或文档质量 ⇒ skills,不是 devx。落点即卡自己提的scripts/pm/check-half-states.mjs。⭐ 分诊本轮独立复现了三个新实例,全部是在正常分诊流程里撞上的
Blocked-by解除条件时对照成立:同一会话、同一通道、同一批工具,本轮成功读取了数十张卡(#13548 · #12824 · #13258 · #13445 …)⇒ 三个失败是关于那三个号码的事实,不是通道故障。
⇒ 连同卡自己的 #13398 / #13399 与 #12775 上的两条裁决评论,已知实例 ≥ 6,而卡明说任何清单都只是下界。
⭐⭐ 而这三次撞上,把卡的论点又推进了一步 —— 一条它没写的推论
⛔ 一张不可达的卡,你读不到它的作者。
三次 404 我都无法确认它们是否与
os-elon同因 —— 因为确认作者需要读到那张卡,而读不到正是问题本身。⇒ 这直接强化了对策 A 的必要性:用点式发现(discovery at point of use)去诊断这个问题,在结构上做不到归因。你只能知道「这个引用断了」,永远不知道「为什么断、还有多少断了」。⇒ 能做的只有巡查,而这正是 A。
为什么 p1(卡未定级,由分诊定)
warnwhere AGENTS.md puts it aterror— and the card that was supposed to carry the level is CLOSED #13398/[Decision]isMissingTableErroris a cross-package contract whose home no plugin can reach — two plugin-auth audit writes stay silently dark because of it #13399 上的裁决评论由zhuangjianguo撰写、本身没有被压制,但因为寄居在别人立的卡上而一并不可达。⇒ 一条裁决的耐久性,被「当初谁碰巧立了那张卡」所界定。assignee照常渲染、引用文本照常存在、没有任何东西变红。⛔ 不判 p0:无用户可见面,且无证据表明有裁决因此被错误执行(只是不可复核)。
范围(A 半边,卡已写好,分诊逐条保留为硬要求)
#引用,对每个去重后的号码解析一次;解析失败即报,连同它的引用方;B 半边已拆出
「一条裁决,只以某一张卡上的一条评论存在时,算不算已记录?」是协议问题,卡自己也把它写成问题而非提案。⇒ 已拆为独立决策卡 #13766,
needs-user-decision+domain:skills。本卡 ⛔ 不承载它,也 ⛔ 不等它 —— A 在 B 的任何答案下都要做。Generated by Claude Code
claude commentedon Aug 31, 2026
os-dev-report
{
"issue": 13634,
"status": "done",
"branch": "claude/issue-13634-dangling-reference-patrol",
"pr": "#13964",
"premise_still_valid": true,
"summary": "Added H40, a report-only row family in scripts/pm/check-half-states.mjs (the only file touched): it sweeps the live board for
#references into this repo, resolves each DISTINCT number once, and reports the ones that fail to resolve with their referrers. All four of triage's hard requirements are mechanised — one resolution per distinct number (cached across the sweep, reusing H19's resolutions); closed is not unreachable (h40Verdict is four-valued: HTTP 200 open OR closed is resolvable and silent, 404 and only 404 is unresolvable, any other failure is UNJUDGED, and budget exhaustion is NOT ATTEMPTED); report-only with no rewrite, no close and no cause asserted; and a cost bound that is measured rather than guessed. Corpus costs zero extra requests (open cards/PRs already held plus comment threads already cached; merged/closed archive deliberately excluded, stated in the report). Resolutions are bounded by H40_RESOLUTION_BUDGET=400 spent newest-first, derived from ~366ms per resolution and a 228s baseline sweep against half-state-patrol.yml's timeout-minutes:15; the live run came in at 360s. Renderer-trimming (#13947) is answered by a RESERVED section plus an unconditional summary clause rather than by finding rows — the reservation charges the block's length to the budget before rows are laid out, so the trim can never reach it; the choice and the two rejected alternatives are argued in the PR body and pinned by three self-test cases. Two cheaper architectures were measured and declined in the header: a bulk state=all listing cannot work because the issues endpoint refuses deep pagination (page 99 answers 200, page 100 answers HTTP 422, so an ascending pass stops at #9,900 on a repo past #13,900) and the descending half reorders under new issues, which would publish a false dangling claim; batched GraphQL was declined because it would run outside this file's REST-shaped prerequisite classifier. Live run finds 45 unresolvable numbers with named referrers where the card's own lower bound was 6 — the card states every list is a lower bound, and 2,306 references below #11410 were not attempted at the budget, so the true figure is higher still. B-half (#13766) neither answered nor waited on. No assignee written (PM owns it). PROTOCOL MISS, reported rather than backfilled: the dispatch asked for my own dev-side claim comment on this card before the first edit and I did not post one. Mutual exclusion was intact throughout regardless — the PM's claim comment 5483090384 already names this exact session id and this exact branch, so no other agent reading the thread could have read the card as free — and a claim comment written after delivery would misstate when the work started, so none was fabricated.","tests": "All at final head 35c684c, exit codes captured before any pipe. (1) pnpm check:pm-half-states: 1826 -> 1922 cases, exit 0. (2) ABLATION, three legs, no build step between this source file and its suite (the suite runs the file directly, so there is no rebuild leg; the on-disk proof stands in its place): each leg proved its anchor unique, counted the injected text (1) and the removed text (0), and confirmed
git hash-objectdiffered from the HEAD blob before running; each restored withgit checkout HEAD -- ABSOLUTE_PATHback to blob 45df30f81c551c16cf02e1d03334b0226b606991 withgit diff HEADempty. Leg 1 (h40Verdict treats any failure as unresolvable) -> 9 red, incl. 'a 403 is UNJUDGED, never a vanished card' and 'a non-404 failure lands in UNJUDGED, not in dangling'. Leg 2 (section leaves the reserved block) -> 3 red, all three #13947 cases. Leg 3 (reserved block's length no longer charged to the budget) -> 2 red (the body-fits case plus H17's existing one). Control on the unmutated tree: exit 0, 1922 pass. A first attempt at leg 2 used an empty replacement; the landing check correctly VOIDED that reading rather than reporting it, and the first control run was red because the failure path skipped restore — both fixed, then re-run. (3) LIVE run, 360s, exit 0: 45 numbers do not resolve, 0 unjudged, 3,725 distinct references off 421 open cards/PRs + 293 cached comment threads, 1,019 free, 400 attempted (355 answered), 2,306 not attempted (floor #11410). Known fixtures reproduced with referrers: #13456 via #13440 #13536 #13634 #13766; #13398 via #6021 #12981 #13634 #13766; #13178 and #13399 likewise. Independent hand controls, 2026-08-31: #13398 #13399 #13178 #12924 #13456 all HTTP 404, while #13324 #13571 (closed) and #13592 (closed PR) resolved 200 alongside four open cards — the closed-is-not-unreachable control. Four new danglers re-resolved by hand (#13462 #13412 #13165 #12924, all 404) and one referrer claim verified against the referring body (#13417 does name #13412 and #13410), so these are not extractor artefacts. (4) GATE UNION derived on the final head vianode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no paths passed): 19 commands, 17 by path + 2 by change KIND (this edits a gate script). All green, quoting each gate's own verdict line. Four are NOT MEASURED locally rather than red, each by its own printed text: check-test-completeness (grades a saved turbo log no local run produces — its own output says 'not a red, and there is nothing here to fix'), check-partof-closing-keyword (NOT WIRED without PR context — separately run green against my actual PR body via PR_BODY), and check-ci-filter-parity / check-closing-keyword-parity / check-shard-attestation which printed PREREQUISITE NOT MET (missingyaml) beforepnpm installand exit 0 after it. (5) Consumer canary pnpm check:pm-clause2-carriers: exit 0, 64 cases — none of the seven imported exports touched; check-governed-merges and ci-failure self-tests also green. (6) pnpm lint (eslint . --no-inline-config, WHOLE repo, no narrowing) through scripts/pm/os-verify-lock.sh: VERDICT command-exit 0, 78s. (7) node scripts/check-nul-bytes.mjs: OK over 7657 files, plus a manual control-byte grep over the edited file and the PR body: no hits.","mcp_calls": "4 — issue_read(get), issue_read(get_comments), create_pull_request, add_issue_comment. Everything else (all resolutions, listings, the label write and every read-back) went through direct REST from this container, which the file's own --probe reports as transport-prerequisite MET.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code