Repository navigation
Node IDs for file / directory / project nodes embed the absolute scan path (leaks local username, makes committed graph.json non-portable / non-idempotent) #1789
Description
Summary
graphify already relativizes the source_file field of every node so the graph
is portable across machines. However, the node id for structural nodes
(files, directories, and — for .NET — .csproj/.sln/solution/project container
nodes) is still built from the absolute resolved path. As a result:
- the committed
graph.jsonleaks the local OS username and full home path
inside those ids, and - the same file produces a different id on a different machine (or a different
checkout location), sographify updateon another clone re-creates those nodes
→ diff churn + non-idempotent output for a versioned graph.
Symbol nodes (classes, functions, methods) are unaffected because their ids are
name/symbol-based; only path-derived structural nodes are impacted.
Environment
- graphify (
graphifyy) 0.8.19 - Windows (but the logic is OS-agnostic; same on Linux/macOS with
/home/<user>/…)
Current behavior
detect()resolves the scan root to an absolute path unconditionally:detect.py→root = root.resolve()(≈ line 866). Every file path handled
downstream is therefore absolute.
- Structural / file nodes get their id from the absolute path string:
extract.py→_make_id(str(path))(e.g. the file-node id assignments around
lines 1684 / 2526 / 2646).pathhere is absolute (from step 1).
- By contrast, symbol nodes use a name-based id:
_make_id(module_name)/
_make_id(str(resolved))— no path, no leak. - Crucially,
graphifyalready relativizessource_filebut not the id:watch.py::_relativize_source_files(≈ lines 131–143):
item["source_file"] = str(source_path.resolve().relative_to(root)), called
from_rebuild_code(≈ line 438). So the scan root is known and the code
already knows how to make paths relative — it just isn't applied to the id.
Concrete example
Project on machine A, user alice:
C:\Users\alice\repos\MyApp\src\Widgets\MyApp.Widgets.csproj
produces a node whose source_file is nicely relative:
{ "source_file": "src/Widgets/MyApp.Widgets.csproj", "label": "MyApp.Widgets.csproj",
"id": "c_users_alice_repos_myapp_src_widgets_myapp_widgets_csproj" }but the id still hard-codes the absolute path (note c_users_alice_repos_myapp_…).
The same repo cloned by user bob to D:\work\MyApp yields the id
d_work_myapp_src_widgets_myapp_widgets_csproj — a different id for the same file.
Directory container nodes behave the same (e.g. a Widgets folder node →
c_users_alice_repos_myapp_src_widgets).
Why this matters
Teams that want to commit graph.json to version control (so colleagues can
query the graph without rebuilding) hit three problems on the affected nodes:
- Privacy / hygiene: the committed file contains each contributor's OS username
and local folder layout. - Non-portability: on a fresh clone the ids don't match the committed graph.
- Non-idempotency / churn:
graphify update(which re-runsdetect()→
absolute root) re-adds absolute-path-id versions of those nodes on every run and
on every different machine, producing noisy, per-contributor diffs.
Today the only workarounds are to (a) exclude the files that create these nodes via
.graphifyignore (loses the info, e.g. the .NET project-dependency graph), or
(b) post-process graph.json to rewrite the ids — both external to graphify.
Reproduction (minimal)
mkdir -p /tmp/demo/src && printf '<Project/>' > /tmp/demo/src/Demo.csproj
graphify /tmp/demo # or: cd /tmp/demo && graphify .
grep -o '"id": *"[^"]*demo[^"]*"' graphify-out/graph.json | head
# -> ids contain the absolute path, e.g. "tmp_demo_src_demo_csproj" and, on a real
# home dir, "<drive>_users_<username>_…"Expected behavior / proposed fix
Make structural/file/directory/project node ids relative to the scan root, the
same way source_file is already relativized. Options, smallest first:
- Relativize before
_make_id: when building a file/dir node id, pass the path
relative toscan_root(the code already computesrelative_to(root)in
_relativize_source_files). This keeps ids stable and portable and requires no
new API — it just makes the id consistent with the already-relativesource_file. - Or add an opt-in flag / env var (e.g.
--relative-ids/GRAPHIFY_RELATIVE_IDS=1)
for teams that commitgraph.json, if a default change is a concern for backward
compatibility of existing graphs.
Either way the goal is: the same source file yields the same node id regardless of
where the repo is checked out, and the committed graph.json never contains an
absolute local path. This would make graph.json a first-class, VCS-friendly
artifact.
Happy to help test a patch. Thanks for graphify!
Activity
safishamsi commentedon Jul 11, 2026
Already fixed in current releases (you're on 0.8.19; current is 0.9.12). On v8,
detect()returns file paths relative to the scan root, so the committedgraph.jsonuses relative structural node ids — no absolute path, no username/home leak. I verified it end-to-end: extracting the same repo from two completely different absolute checkout paths (/tmp/alice_home/projvs/tmp/bob_elsewhere/checkout/proj) produces identical node ids (pkg_app,pkg_mod, …), and directory/.csproj/.sln container nodes are all relative too. Added a regression test locking cross-checkout id portability.Note: if you call the
extract()Python API directly with absolute paths (rather than the CLI), you'll still get absolute-embedded ids — but the CLI, which produces the committedgraph.json, relativizes. Pleaseuv tool install --upgrade graphifyy; 0.8.19 predates the fix. Thanks for the precise write-up.test(cli): graph.json node ids are portable across checkout paths (#1789
fremat79 commentedon Jul 12, 2026
Hi, and thank you very much for looking into this and for the fix — really appreciated.
I'm sorry to reopen this, and it's entirely possible I'm doing something wrong on my side, but unfortunately after updating to the latest release we're still seeing the same behaviour in our tests. I put together a tiny reproduction in case it helps, and I apologise in advance if it turns out to be a mistake at our end.
Environment
0.9.13(installed viauv tool;graphify --versionreportsgraphify 0.9.13)Minimal reproduction
I've attached
graphify-1789-repro.zip. It's a tiny .NET solution and needs nothing but graphify itself — no .NET SDK, no build, no API key. Full steps are inREPRODUCE.mdinside the zip; briefly:cd graphify-1789-reprographify update .python check_ids.py(or just opengraphify-out/graph.json)What we see
The structural container nodes still carry the absolute local path — including the username — in their
id, whilesource_fileis already relative. In this minimal case two nodes are affected (thePluginsandServicessolution folders):On our real solutions the same happens for the per-folder/solution nodes, which unfortunately ends up committing our usernames into
graph.jsonand produces per-machine diffs ongraphify update.What we expected
That the
idwould be a slug relative to the scan root, likesource_filealready is — with no absolute path or username (e.g.id = plugins).Sorry again if I've simply missed a setting or a newer build. Please let me know if there's anything else I can provide, and thanks again for your work on graphify.
graphify-1789-repro.zip
safishamsi commentedon Jul 12, 2026
You're right, and thank you for the clear follow-up, @fremat79. I closed this prematurely: the 0.9.13 fix relativized
.sln/.csprojfile node ids, but I never checked the virtual solution-folder nodes, and those still carried the absolute local path (with the username) in theirid.I reproduced it independently with a minimal solution containing solution folders and confirmed the leak, so I did not need to open your attachment.
Root cause is in
extract_sln: a solution folder is a virtual grouping, not a file (Visual Studio writes its name as both the display name and the "path"), but the extractor resolved it to an absolute filesystem path and keyed the node id off that. The CLI's id-relativization pass only remaps ids of real files in the scan set, so a virtual folder never matched and its absolute id survived intograph.json.Fixed in
373bc8e: solution folders are now detected (name == path) and keyed off the folder name only, so you getid=pluginsas expected; real project files still resolve as before. Added a regression test for it. This ships in 0.9.14.Sorry for the churn on this one.
fix(sln): keep Visual Studio solution-folder node ids relative (#1789)
safishamsi commentedon Jul 13, 2026
Shipped in 0.9.14 (now on PyPI:
pip install --upgrade graphifyy). The solution-folder node-id leak is fixed in373bc8e— virtual solution folders are keyed off the folder name only (id=plugins), so no absolute path or username lands ingraph.json, while real project files still resolve as before. Verified against the 0.9.14 wheel with nested folders and folder names containing spaces. Thanks again @fremat79 for the precise repro and for catching that the earlier close was premature.fix(extract): close residual absolute-path/username leaks (#1899)
Squashed commit of the following:
test(cli): graph.json node ids are portable across checkout paths (Gr…
fix(sln): keep Visual Studio solution-folder node ids relative (Graph…
fix(extract): close residual absolute-path/username leaks (Graphify-L…