Problem
idd-list Step 3.5 builds the issue→PR index with PR_REF_RE = #(\d{1,7})\b and treats every match as an issue number. GitHub shares one numbering space between issues and pull requests, so a PR that cross-references another PR produces a phantom issue ref — and that phantom can become a cluster leader.
The skill is explicit that the regex "偵測任何 #NNN 提及", and the surrounding text reasons only about digit-length and #0. Neither guard separates issues from PRs.
Measured
Running /idd-list (no flags) against PsychQuant/che-word-mcp today, with each referenced number then classified by hand:
Because len(refs) >= 2 in every case, all three were classified as clusters. The rendered output was:
#172 [no phase] protect_document is a stub — …
└─ → see PR #171 (cluster member, leader #83)
#90 [implemented] P3 enhancement: H₀ Unicode subscript anchors …
└─ → see PR #115 (cluster member, leader #38)
→ see PR #171 (cluster member, leader #83) sends the reader to find issue #83. There is no issue #83. The row it points at cannot exist in any view, at any --limit, under any filter.
Both should have rendered as solo-PR lines:
#172 └─ PR #171 (ready, UNKNOWN)
#90 └─ PR #115 (ready, UNKNOWN)
Why the existing dangling-leader mitigation does not cover this
Step 4 already has a surrogate-leader fallback for min(cluster_members) not being in the current view, attributed to --label filtering, --limit truncation, or a closed leader. A PR-number leader is never in the view, so if that path fires at all it reports a cause that is not the cause — the reader is told the leader was filtered out, and goes looking for it.
More importantly the fallback repairs the rendering of a cluster that should not have been detected in the first place. The footer inherits the error directly: N issues bundled in M cluster(s) counts PRs and closed issues as bundled issues.
Second-order: closed issues also inflate clusters
Independent of the PR confusion, #38 (closed) and #84 (closed) count toward len(refs) >= 2 in a --state open listing. A PR whose only open referent is one issue is a solo PR from the reader's point of view. Same defect shape, different axis, and the two compound: PR #171's "cluster" survives only because one PR and one closed issue are both being counted.
Suggested direction
Classify each extracted number once, before cluster detection:
gh api "repos/$OWNER/$REPO/issues/$N" --jq 'if .pull_request then "PR" else "issue" end'
The issues endpoint returns PRs too, distinguished by the presence of a pull_request key — so one call answers both "is it an issue" and "what state is it in".
Two things keep the cost bounded, and they matter because Step 2.5 already argues against N+1 queries:
- Most refs need no call at all. The issue list is already fetched; any number present in it is a known-good issue. Only refs outside the fetched set need classifying.
- Classify per distinct ref, not per issue-PR pair, and cache within the run. In the measurement above that is 10 numbers total across 3 PRs.
A GraphQL batch (nodes(ids:) or aliased issueOrPullRequest(number:)) collapses it to one request if the call count still matters.
Then: drop PR-numbered refs entirely, and compute cluster_members from refs that are open issues — matching what a --state open listing claims to be showing.
Impact
Wrong routing that looks right. The table renders cleanly, the syntax is valid, nothing warns, and the suggested next action points at an issue number that does not exist. That is the same failure shape #298 documents for the four blocked-state signals: "9 條路由裡 8 條錯,而且錯得完全看不出來".
Problem
idd-listStep 3.5 builds the issue→PR index withPR_REF_RE = #(\d{1,7})\band treats every match as an issue number. GitHub shares one numbering space between issues and pull requests, so a PR that cross-references another PR produces a phantom issue ref — and that phantom can become a cluster leader.The skill is explicit that the regex "偵測任何
#NNN提及", and the surrounding text reasons only about digit-length and#0. Neither guard separates issues from PRs.Measured
Running
/idd-list(no flags) againstPsychQuant/che-word-mcptoday, with each referenced number then classified by hand:Because
len(refs) >= 2in every case, all three were classified as clusters. The rendered output was:→ see PR #171 (cluster member, leader #83)sends the reader to find issue #83. There is no issue #83. The row it points at cannot exist in any view, at any--limit, under any filter.Both should have rendered as solo-PR lines:
Why the existing dangling-leader mitigation does not cover this
Step 4 already has a surrogate-leader fallback for
min(cluster_members)not being in the current view, attributed to--labelfiltering,--limittruncation, or a closed leader. A PR-number leader is never in the view, so if that path fires at all it reports a cause that is not the cause — the reader is told the leader was filtered out, and goes looking for it.More importantly the fallback repairs the rendering of a cluster that should not have been detected in the first place. The footer inherits the error directly:
N issues bundled in M cluster(s)counts PRs and closed issues as bundled issues.Second-order: closed issues also inflate clusters
Independent of the PR confusion,
#38(closed) and#84(closed) count towardlen(refs) >= 2in a--state openlisting. A PR whose only open referent is one issue is a solo PR from the reader's point of view. Same defect shape, different axis, and the two compound: PR #171's "cluster" survives only because one PR and one closed issue are both being counted.Suggested direction
Classify each extracted number once, before cluster detection:
The
issuesendpoint returns PRs too, distinguished by the presence of apull_requestkey — so one call answers both "is it an issue" and "what state is it in".Two things keep the cost bounded, and they matter because Step 2.5 already argues against N+1 queries:
A GraphQL batch (
nodes(ids:)or aliasedissueOrPullRequest(number:)) collapses it to one request if the call count still matters.Then: drop PR-numbered refs entirely, and compute
cluster_membersfrom refs that are open issues — matching what a--state openlisting claims to be showing.Impact
Wrong routing that looks right. The table renders cleanly, the syntax is valid, nothing warns, and the suggested next action points at an issue number that does not exist. That is the same failure shape #298 documents for the four blocked-state signals: "9 條路由裡 8 條錯,而且錯得完全看不出來".