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.json leaks 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), so graphify update on 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). path here 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,
graphify already relativizes source_file but 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-runs detect() →
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 to scan_root (the code already computes relative_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-relative source_file.
- Or add an opt-in flag / env var (e.g.
--relative-ids / GRAPHIFY_RELATIVE_IDS=1)
for teams that commit graph.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!
Summary
graphifyalready relativizes thesource_filefield of every node so the graphis portable across machines. However, the node
idfor structural nodes(files, directories, and — for .NET —
.csproj/.sln/solution/project containernodes) is still built from the absolute resolved path. As a result:
graph.jsonleaks the local OS username and full home pathinside those ids, and
checkout location), so
graphify 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
graphifyy) 0.8.19/home/<user>/…)Current behavior
detect()resolves the scan root to an absolute path unconditionally:detect.py→root = root.resolve()(≈ line 866). Every file path handleddownstream is therefore absolute.
extract.py→_make_id(str(path))(e.g. the file-node id assignments aroundlines 1684 / 2526 / 2646).
pathhere is absolute (from step 1)._make_id(module_name)/_make_id(str(resolved))— no path, no leak.graphifyalready relativizessource_filebut not the id:watch.py::_relativize_source_files(≈ lines 131–143):item["source_file"] = str(source_path.resolve().relative_to(root)), calledfrom
_rebuild_code(≈ line 438). So the scan root is known and the codealready knows how to make paths relative — it just isn't applied to the id.
Concrete example
Project on machine A, user
alice:produces a node whose
source_fileis 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
idstill hard-codes the absolute path (notec_users_alice_repos_myapp_…).The same repo cloned by user
bobtoD:\work\MyAppyields the idd_work_myapp_src_widgets_myapp_widgets_csproj— a different id for the same file.Directory container nodes behave the same (e.g. a
Widgetsfolder node →c_users_alice_repos_myapp_src_widgets).Why this matters
Teams that want to commit
graph.jsonto version control (so colleagues canquery the graph without rebuilding) hit three problems on the affected nodes:
and local folder layout.
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.jsonto rewrite the ids — both external to graphify.Reproduction (minimal)
Expected behavior / proposed fix
Make structural/file/directory/project node ids relative to the scan root, the
same way
source_fileis already relativized. Options, smallest first:_make_id: when building a file/dir node id, pass the pathrelative to
scan_root(the code already computesrelative_to(root)in_relativize_source_files). This keeps ids stable and portable and requires nonew API — it just makes the id consistent with the already-relative
source_file.--relative-ids/GRAPHIFY_RELATIVE_IDS=1)for teams that commit
graph.json, if a default change is a concern for backwardcompatibility 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.jsonnever contains anabsolute local path. This would make
graph.jsona first-class, VCS-friendlyartifact.
Happy to help test a patch. Thanks for graphify!