Repository navigation
[Bug]:merged graph — ambiguity refusal reports cross-repo same-basename nodes as being in one file #4512
Description
Pre-flight checks
- I have checked the Troubleshooting section in the README
What happened?
In a merged (multi-repo) graph, when an ambiguous name resolves to nodes whose repo-relative paths are identical, the ambiguity refusal misreports the nodes as being in one file and then lists only line numbers — so the message no longer identifies which file (or which repo) each candidate belongs to, and the suggested recovery can't be applied.
Both explain and path are affected (exit code 3, so the refusal itself is correct — only the message is wrong).
The trigger is colliding basenames across repos, which is common in merged graphs: web/dup.py and ml/dup.py, or any __init__.py / index.ts / mod.rs repeated across repos.
For contrast, the ordinary single-repo case (two different relative paths) reports correctly.
Steps to reproduce
mkdir -p repro/web repro/ml && cd repro
cat > web/dup.py <<'EOF'
class Config: pass
EOF
cat > ml/dup.py <<'EOF'
class Config: pass
EOF
graphify extract web --code-only --no-label
graphify extract ml --code-only --no-label
graphify merge-graphs web/graphify-out/graph.json ml/graphify-out/graph.json \
--out m/graph.json
Both nodes survive the merge with distinct namespaced ids, but an identical `source_file`:
web::dup_config label "Config" source_file "dup.py" repo "web"
ml::dup_config label "Config" source_file "dup.py" repo "ml"Error output or graph output
**Colliding basenames — the broken case:**
$ graphify explain "Config" --graph m/graph.json
Ambiguous: 'Config' matches 2 nodes in dup.py.
L1
id: web::dup_config
L1
id: ml::dup_config
Retry with the full node id.
$ echo $?
3
$ graphify path "Config" "Config" --graph m/graph.json
Ambiguous: 'Config' matches 2 nodes in dup.py.
L1
id: web::dup_config
L1
id: ml::dup_config
Retry with the full node id.
$ echo $?
3
Problems with that output, in order of severity:
1. **`in dup.py` is wrong** — there are two `dup.py`, in different repositories.
2. **The listing shows only `L1`** for both, so a reader cannot tell the candidates apart at all; the `web::` / `ml::` prefix in the `id` is the only distinguishing information present.
3. **It falls into the same-file form of the message**, `Retry with the full node id.` — which is *less* actionable than the cross-file form's `Retry with path::symbol ...`, and the `path::symbol` form is the one that actually resolves it here.
**Distinct relative paths — the correct case (unchanged behavior):**
$ graphify explain "Config" # pkg/a.py + pkg/sub/b.py in one repo
Ambiguous: 'Config' matches 2 nodes in different files.
pkg/a.py
id: pkg_a_config
pkg/sub/b.py
id: pkg_sub_b_config
Retry with path::symbol using one of the paths above (e.g. <path>::Config) or the full node id.
## Likely cause
`serve._resolve_single_node` appears to decide same-file vs cross-file from `source_file` alone, and `source_file` is repo-relative and unprefixed in a merged graph — so two nodes from different repos compare equal and take the same-file branch:
def _same_file_ambiguity_message(G, label, hits):
source = G.nodes[hits[0]].get("source_file") # 'dup.py' for both nodes
where = f"in {source}" if source else "with no source file"
listing = "\n".join(
f" {G.nodes[h].get('source_location') or G.nodes[h].get('label', h)}\n id: {h}"
for h in hits
)
Note the listing uses `source_location` (line only) for the same-file branch, which is reasonable when the file really is shared — that assumption is what breaks here.
The information needed to fix it is already on the nodes: a `repo` attribute is present on merged nodes (`"repo": "web"` / `"repo": "ml"`, next to `local_id`), and it is absent/`None` for a single-repo build.
Possible directions (your call on which fits):
- treat `(repo, source_file)` as the file identity when deciding same-file vs cross-file, so this case takes the existing cross-file branch and its `path::symbol` hint; or
- keep the branch but render repo-qualified paths (`web/dup.py` / `ml/dup.py`) in both the `where` clause and the listing.
Either way, `web::dup_config` and `ml::dup_config` should not both render as bare `L1`.
Non-goal: this is unrelated to the `explain` resolution policy in #1969 — resolution correctly refuses here; only the message loses information.
Graphify version
0.9.92
Operating System
Linux
Python Version
3.14.4
Installation Method
uv tool install (recommended)
Additional Environment Details
Python 3.14.4 / Ubuntu 26.04; extras chinese,leiden,watch,mcp,pdf,office,svg. Graph built with graphify extract <dir> --code-only --no-label; merge via graphify merge-graphs a/graphify-out/graph.json b/graphify-out/graph.json --out m/graph.json. Same-filename collisions also occur for __init__.py, index.ts, mod.rs, utils.py and similar names that repeat across repositories in a merged or --global graph.
Additional context
No response
Activity
github-actions commentedon Oct 17, 2026
Thanks for opening this issue, @kaka-in-home. A maintainer will take a look soon.
If you would like to discuss it in real time, come say hi on our Discord server. For longer-form questions and ideas there is also GitHub Discussions.
To help us triage, please make sure the report includes what you expected, what actually happened, and the steps (and a small sample) to reproduce it.
[-][Bug]:[/-][+][Bug]:merged graph — ambiguity refusal reports cross-repo same-basename nodes as being in one file[/+]test: same-named files from two repos are ambiguous (Graphify-Labs#4512)
fix: same-named files from two repos are not one file (Graphify-Labs#…
fix: same-named files from two repos are not one file in explain and …
safishamsi commentedon Oct 20, 2026
Fixed in 0.9.93 by #4520. Same-named files from two repos are kept distinct in
explainandpath. Thanks @kaka-in-home for the report. Live on PyPI: https://pypi.org/project/graphifyy/0.9.93/