Skip to content

fix(cli): make graphify path deterministic across runs (#2074)#2087

Closed
HerenderKumar wants to merge 1 commit into
Graphify-Labs:v8from
HerenderKumar:fix-path-nondeterminism
Closed

fix(cli): make graphify path deterministic across runs (#2074)#2087
HerenderKumar wants to merge 1 commit into
Graphify-Labs:v8from
HerenderKumar:fix-path-nondeterminism

Conversation

@HerenderKumar

Copy link
Copy Markdown
Contributor

Fixes #2074.

graphify path A B returned a different equal-length route on identical, repeated calls against an unchanged graph.json.

Root cause

path loads the graph via node_link_graph, and graph.json carries no multigraph key, so it comes back as a MultiDiGraph. The route is then computed with nx.shortest_path, which returns whichever one of the equal-length shortest paths the adjacency-iteration order reaches first — and Python's per-process hash randomization shuffles that order. Same graph, a different route each run.

Reproduced on a diamond graph (a→b→d, a→c→d): graphify path a d flips between routes purely by PYTHONHASHSEED, no edits in between.

Fix

Select the canonical shortest path instead: the lexicographically smallest by node-id sequence among all_shortest_paths. Deterministic regardless of hash seed (matches the reporter's stable-sort-by-node-id suggestion). With the route stable, the relation rendered for each hop is stable too.

Test

test_path_is_deterministic_across_hash_seeds spawns graphify path across 12 hash seeds and asserts one identical route. Red-green verified: it fails on the old code (routes vary) and passes after the fix.

Out of scope

The reported phantom --calls--> (a relation that doesn't match the two labeled nodes) is a label-collision artifact — path prints node labels but traverses node IDs, and distinct IDs can share a label — i.e. issue #1829. This PR fixes the non-determinism; the label disambiguation belongs with #1829.

…#2074)

`graphify path A B` could return a different equal-length route on identical,
repeated calls against an unchanged graph.json. `path` loads the graph via
node_link_graph with no `multigraph` key, so it becomes a MultiDiGraph, and
`nx.shortest_path` returns whichever equal-length path the adjacency iteration
order reaches first — an order Python's per-process hash randomization shuffles.
Same graph, a different route each run.

Select the canonical shortest path instead: the lexicographically smallest by
node-id sequence among all_shortest_paths. Deterministic regardless of hash
seed (matches the reporter's stable-sort-by-node-id suggestion). With the route
now stable, the relation label rendered from the chosen edge is stable too.

Test spawns `graphify path` across 12 hash seeds and asserts one identical route.
@safishamsi

Copy link
Copy Markdown
Collaborator

Thanks @HerenderKumar#2074 was fixed in v0.9.23: graphify path (and the MCP shortest_path tool) now traverse a sorted, materialized undirected graph so the route is deterministic across runs regardless of hash seed, plus the hop labels now show the edge's real relation. Landed in parallel while triaging the batch, so closing this as resolved. https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.23

@safishamsi safishamsi closed this Jul 21, 2026
@HerenderKumar
HerenderKumar deleted the fix-path-nondeterminism branch July 22, 2026 04:30
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 path: non-deterministic shortest-path route + calls label not backed by any stored edge

2 participants