Skip to content

LLM community labels are discarded on ANY membership change — a one-node delta renames a 200-node community after its hub file #2298

Description

@nuboxworld-byte

Version: 0.9.29
Affects: watch / update / the post-commit + post-checkout hooks, on any repo where graphify label has been run.

Summary

After graphify label, every rebuild revalidates each reused label against a membership fingerprint:

# cluster.py:113 — sha256 over the exact sorted member id list
sigs[cid] = h.hexdigest()[:16]
# watch.py:1327
stale = {cid for cid in labels if saved_sigs.get(cid) != cur_sigs.get(cid)}

The intent is right and the comment says so plainly — reusing a label on a community that has become a different community is the stale-label bug this guards against, and hub-naming is correct-by-construction as a fallback.

But the test is exact set equality. A community that gains or loses a single node fails it. Since remap_communities_to_previous has already re-aligned cids by node overlap, the comparison is between the same conceptual community's old and new membership — so a 200-node community that absorbs one new symbol is judged "different" and its LLM name is replaced by its hub filename.

Any code edit reshuffles some memberships, so labels decay on ordinary work.

Measurement

Two independent observations on a 5.8k-node repo, right after a full graphify label --backend claude-cli run that produced plain-language names for every community:

trigger communities named in plain language lost to hub filenames
git checkout -b <new-branch> 491 → 410 81 (16%)
git checkout main 576 → 488 87 (15%)

No code was edited between labelling and the checkout in either case — switching branches was enough. The names lost are the useful ones (Alert Engine & Jobs, Location Hours Engine); what replaces them is Summary.tsx, payment/repo.ts, wire.ts.

The practical consequence is that label cannot be treated as a durable step. On this repo it costs ~15 LLM calls to restore, and the next branch switch undoes ~15% of it again, so GRAPH_REPORT.md drifts toward filenames between runs.

Why "just re-run label" is not the answer

Re-running is not idempotent-cheap: it re-clusters, and on a rate-limited backend a partial run is actively harmful — batches that fail fall back to hub names and are written, so an interrupted refresh can leave the graph worse than before it started. (Observed here: a retry that hit a provider daily cap at batch 12 of 20 turned 63 filename-names into 263. Recovered from the <date>/ backup graphify writes.) That is arguably its own issue; it matters here because it removes the obvious workaround.

Suggested fix

Invalidate on substantial change rather than any change. A community whose membership is 99% the same is the same community and its name still describes it; one that shares 20% is not.

_minhash.py already ships MinHash/MinHashLSH in this codebase, so the machinery exists:

  1. Persist a MinHash (or the sorted member list) per community in the .sig sidecar instead of a truncated sha256, and invalidate when Jaccard similarity to the saved set falls below a threshold (0.7 seems a reasonable default — happy to be told otherwise).
  2. Keep the current exact-hash path as the fast accept: identical signature → reuse, no similarity computation.
  3. Sidecar format change needs a version marker; a sidecar in the old format can fall back to today's exact-equality behaviour, so existing installs degrade to current behaviour rather than breaking.

A cheaper variant if changing the sidecar is unwelcome: store the member count alongside the hash and treat a delta under N% as non-stale. Weaker (it cannot tell a swap from a no-op) but a one-line sidecar change.

I have a checkout with tests set up from #2294 and can implement whichever direction you prefer — I would rather not guess at the threshold policy in a PR, since it is a judgement call about how much drift makes a name wrong.

Related: #2073 (placeholder perpetuation), #1027 (cid misalignment), #822 (overlap remap).

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