#2540·graphrag

[Bug]: update_communities leaves dangling entity_ids/relationship_ids after an incremental update

Author: mainpartCreated Sep 3, 2026Updated Sep 6, 2026

Do you need to file an issue?

  • I have searched the existing issues and this bug is not already filed.
  • My model is hosted on OpenAI or Azure. If not, please look at the "model providers" issue and don't file a new one here.
  • I believe this is a legitimate bug, not just a question. If this is a question, please use the Discussions area.

Describe the bug

graphrag update runs the full pipeline over the new document alone, minting fresh UUIDs, then merges the result into the existing index. The entity merge keeps the pre-existing row on a title collision, discards the delta UUID and returns a {delta_id: old_id} mapping. The community merge never receives it, so delta communities are concatenated with their entity_ids and relationship_ids still pointing at the discarded ids.

Minimal example

Initial graphrag index over document A:

entities      ALPHA id=e-1,  BETA id=e-2
communities   community 0 -> entity_ids [e-1, e-2]

graphrag update with document B, which mentions ALPHA and GAMMA. The delta run has no knowledge of the existing index, so it mints new ids and clusters its own graph:

delta entities      ALPHA id=d-1,  GAMMA id=d-2
delta communities   community 0 -> entity_ids [d-1, d-2]

After the merge:

entities      ALPHA id=e-1     (d-1 discarded; mapping {d-1: e-1} produced)
              BETA  id=e-2
              GAMMA id=d-2
communities   community 0 -> entity_ids [e-1, e-2]
              community 1 -> entity_ids [d-1, d-2]   <-- d-1 resolves to nothing

Root cause

_group_and_resolve_entities keeps the old row on a title collision and returns {delta_id: old_id}:

python
id_mapping = dict(zip(merged["id_B"], merged["id_A"]))   # delta -> old
...
.groupby("title").agg({"id": "first", ...})              # old row wins

The mapping is published to the run state in update_entities_relationships.py, and applied in exactly one place: update_text_units.py. _update_and_merge_communities never touches entity_ids or relationship_ids; they ride through the concatenation unchanged.

Relationships are worse off: _update_and_merge_relationships uses the same "id": "first" collision handling but returns only a DataFrame, so no relationship mapping exists at all.

Impact

  • ALPHA is never linked to community 1, although the report for community 1 was generated from ALPHA's delta description and refers to it.
  • communities.size for community 1 says 2; only one member is resolvable.
  • The same applies to relationship_ids for any edge present in both documents.

Related issues

  • #2348 (closed as not_planned) and #2259 — the left join in read_indexer_entities that resolves community membership by entity id. Confirms that an id which fails to resolve raises nothing and logs nothing; entities left with level = NaN are silently dropped by df[df.level <= community_level].
  • #1702 / #1734 — precedent. A missing identity reconciliation in this same merge step was accepted and fixed. The function quoted there, _update_and_merge_text_units, is the only place entity_id_mapping is applied today.

Steps to reproduce

  1. graphrag index a corpus containing at least one entity that will also appear in a second document.
  2. Add the second document and run graphrag update.
  3. Read communities.parquet and entities.parquet, and explode entity_ids:
python
import pandas as pd
e = pd.read_parquet("output/entities.parquet")
c = pd.read_parquet("output/communities.parquet")
exploded = c.explode("entity_ids")
dangling = exploded[~exploded["entity_ids"].isin(set(e["id"]))]
print(dangling[["community", "entity_ids"]])

Every row printed is a community member that no query path can resolve. The count grows with each subsequent update.

Expected Behavior

Community membership lists should reference the ids that survived the entity and relationship merges, so that entities carried over from a previous run remain linked to the communities the delta run placed them in.