Skip to content

fix(explain): render arrows in original edge direction (#853)#854

Closed
spindle79 wants to merge 1 commit into
Graphify-Labs:v7from
spindle79:fix/explain-arrow-direction
Closed

fix(explain): render arrows in original edge direction (#853)#854
spindle79 wants to merge 1 commit into
Graphify-Labs:v7from
spindle79:fix/explain-arrow-direction

Conversation

@spindle79

Copy link
Copy Markdown
Contributor

Fixes #853.

Summary

Same structural bug as #849, same fix shape as PR #851 — applied to graphify explain. Previously every connection rendered as --> X [relation], asserting a backwards call graph whenever the queried node was a callee.

Reproducer

Real-world: explain validateSanitySession() printed --> createPatchHandler() [calls] even though the stored edge is createPatchHandler --calls--> validateSanitySession (verified in graph.json and by the file source). validateSanitySession() is a pure leaf utility — all 13 of its connections are inbound — yet every one rendered as outbound.

Fix

Same as #851:

  • Force directed interpretation on read (_raw = {**_raw, "directed": True}) so the loaded G is a DiGraph preserving edge direction.
  • Iterate G.successors(nid) and G.predecessors(nid) separately, tagging each connection as "out" or "in".
  • Render --> for outbound, <-- for inbound.

A neighbor that has both an inbound and outbound edge to the queried node (mutual references) is rendered twice — once per edge. That's the truthful directed representation; the previous undirected G.neighbors() would have silently collapsed it to one row.

Tests

tests/test_explain_cli.py — two tests, both on a graph.json fixture whose on-disk shape matches what graphify update produces (directed: false, links[i].source = caller):

  1. test_explain_renders_callers_as_inbound_and_callees_as_outbound — queried node is validateSanitySession. Its two callers (createPatchHandler, createEditHandler) must render as <--; its own callee (stableStringify) must render as -->. Asserts the old buggy --> createPatchHandler() [calls] is not in the output.
  2. test_explain_renders_pure_caller_as_outbound — converse: querying createPatchHandler must show validateSanitySession as --> and no <-- rows.

The fixture writes graph.json directly rather than via nx.Graph().add_edge() + node_link_data() — the latter emits links in node-insertion order, not edge-argument order, which can silently encode the wrong direction in small fixtures and mask the bug.

Test plan

Notes for the reviewer

  • If fix(path): render arrows in original edge direction (#849) #851 has already merged, this branch may need a trivial rebase (both touch adjacent regions of __main__.py for path/explain), but the two changes are independent.
  • The directed-on-read interpretation is safe even for graphs serialized with directed: false because links[].source/.target are preserved verbatim by node_link_graph and always represent caller→callee per the existing build conventions — verified empirically on real graphs.

Same structural bug as Graphify-Labs#849 (and the same fix shape). `graphify explain`
previously iterated `G.neighbors(nid)` on the undirected loaded graph and
printed every connection as `--> X [relation]`, ignoring whether the
underlying edge actually pointed out of the queried node or into it.

For a queried callee, its callers were rendered as outbound calls —
asserting a backwards call graph. Confirmed in the wild on a real
codebase: `explain validateSanitySession()` printed
`--> createPatchHandler() [calls]` even though the stored edge is
`createPatchHandler --calls--> validateSanitySession`.

Fix mirrors Graphify-Labs#849:

- Force directed interpretation on read (`_raw = {**_raw, "directed": True}`)
  so the loaded `G` is a `DiGraph` that preserves edge direction.
- Iterate successors and predecessors separately, tagging each connection
  as "out" or "in".
- Render `-->` for outbound, `<--` for inbound.

A neighbor that has both an inbound AND outbound edge to the queried node
(mutual references) is rendered twice — once per edge — which is the
truthful directed representation.

Tests in `tests/test_explain_cli.py` cover both directions.
@safishamsi

Copy link
Copy Markdown
Collaborator

Thanks for the report! We shipped this fix ourselves in commit 7bb0919 (v0.7.17) — explain arrow direction now correctly shows --> for successors and <-- for predecessors in directed graphs. Closing since the fix is already in main.

@safishamsi safishamsi closed this May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

graphify explain renders inbound edges as outbound (twin of #849)

2 participants