Skip to content

idd-list treats PR numbers as issue numbers, inventing clusters led by non-issues #326

Description

@kiki830621

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:

PR refs in body what they actually are
#171 #83, #84, #172 #83 is a MERGED PR; #84 is a closed issue; only #172 is an open issue
#115 #38, #90 #38 is a closed issue; only #90 is an open issue
#156 #114, #129, #143, #145, #146 #114 is a closed PR; the other four are open issues

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:

  1. 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.
  2. 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 條錯,而且錯得完全看不出來".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions