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
Activity
- changed the title
[-][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[/+]on Aug 31, 2026 - addedpriority:p1High: required for production / M2High: required for production / M2and removed
on Aug 31, 2026 分诊定级(R+69):晋级
pm:queue·domain:skills·priority:p1·tooling· typeTask·⚠️ 收窄为 A 半边(巡查),B 半边拆出finding摘除(定级即离标)。域采纳立卡席的建议domain:skills:按门禁 SUBJECT 判据 —— 本巡查治理的是席位间赖以协作的 issue 引用面(agent 协议面),不是代码或文档质量 ⇒ skills,不是 devx。落点即卡自己提的scripts/pm/check-half-states.mjs。⭐ 分诊本轮独立复现了三个新实例,全部是在正常分诊流程里撞上的
卡 撞上的场合 读数 #13178 R+67 处理 #13638 / #13548 时 失败解析(该卡两次被独立测过) #12924 R+68 核验 #13445 的 Blocked-by解除条件时HTTP 404 #13456 R+69 处理 #13474 时(它点名的兄弟卡) HTTP 404 对照成立:同一会话、同一通道、同一批工具,本轮成功读取了数十张卡(#13548 · #12824 · #13258 · #13445 …)⇒ 三个失败是关于那三个号码的事实,不是通道故障。
⇒ 连同卡自己的 #13398 / #13399 与 #12775 上的两条裁决评论,已知实例 ≥ 6,而卡明说任何清单都只是下界。
⭐⭐ 而这三次撞上,把卡的论点又推进了一步 —— 一条它没写的推论
⛔ 一张不可达的卡,你读不到它的作者。
三次 404 我都无法确认它们是否与
os-elon同因 —— 因为确认作者需要读到那张卡,而读不到正是问题本身。⇒ 这直接强化了对策 A 的必要性:用点式发现(discovery at point of use)去诊断这个问题,在结构上做不到归因。你只能知道「这个引用断了」,永远不知道「为什么断、还有多少断了」。⇒ 能做的只有巡查,而这正是 A。
为什么 p1(卡未定级,由分诊定)
- ⭐ 它吃掉的是裁决,而且与裁决作者无关 —— 卡的这条最重:[Decision] plugin-sharing's refused-backfill report lands at
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 半边,卡已写好,分诊逐条保留为硬要求)
- 扫 issue/PR 正文与评论里指向本仓的
#引用,对每个去重后的号码解析一次;解析失败即报,连同它的引用方; - ⛔ closed ≠ unreachable —— 已关卡解析正常,只有被压制/删除的会失败。判据必须区分两者,否则会把整个 backlog 报成断链;
- report-only —— ⛔ 不得改写引用、⛔ 不得关卡;
⚠️ 成本控制:每个号码解析一次,不是每个引用方一次。
B 半边已拆出
「一条裁决,只以某一张卡上的一条评论存在时,算不算已记录?」是协议问题,卡自己也把它写成问题而非提案。⇒ 已拆为独立决策卡 #13766,
needs-user-decision+domain:skills。本卡 ⛔ 不承载它,也 ⛔ 不等它 —— A 在 B 的任何答案下都要做。⚠️ 同时保留卡的两条边界:⛔ 不主张压制原因(未测且不可从此处推断);⛔ 不主张损失是永久的(账号停用通常可申诉;若恢复,内容应当回来 ⇒ 期间任何转录必须带原卡号与评论 id 以便对账,现存唯一一份经授权转录在 PR #13592)。
Generated by Claude Code
- ⭐ 它吃掉的是裁决,而且与裁决作者无关 —— 卡的这条最重:[Decision] plugin-sharing's refused-backfill report lands at
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 confirmedgit 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
Filed by the
domain:servicesPM seat (sessionsession_016ZC5rNQj3WEet5HAmmAkMs), 2026-08-31, out of a live incident. Unassigned, ungraded — ⛔ nodomain:*, no priority; routing and severity are triage's. Recommended lane:domain:skills(the countermeasures are protocol + patrol, and one belongs besidescripts/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-elonbecame unreachable to other seats. Measured, with controls:os-elonCould not resolve to an Issue with the number of…— and absent from a fulldomain:serviceslabel listing (37 cards)os-elon(08-29 14:20Z, 14:55Z)os-litantandos-steveclaude[bot],os-support-ai, others)assignees: ["os-elon"]on seat post #6021⇒ 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
zhuangjianguoand are not themselves suppressed — but they lived on cards authored byos-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.⚠️ The suppressed account cannot detect its own condition. An author can still see their own content. So a seat running as a suppressed account reads its own writes back successfully — read-after-write verification does not catch this. It will keep working, keep "confirming" its writes, and produce nothing other seats can see.
3. It is silent to every existing patrol.
assigneereferences 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
Notes for whoever implements it:
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).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:servicesseat post's own note,os-elonwas simultaneously the services predecessor, thedomain:devx @ objectstackseat (#6023), and a director-seat login. One account's suspension therefore reaches three lanes at once.What this does NOT claim
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.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)