Repository navigation
Absolute-path node ids on 0.9.25 for in-root relative import targets (residual of #1899 Variant B; ghost duplicates + dangling endpoints) #2195
Description
Summary
On 0.9.25, node ids for the targets of relative imports that cross directories are still built from the absolute resolved path, while the same node's source_file is correctly relativized. This is the same class as #1789 / #1899 (Variant B: relativized source_file but an id derived from the absolute path) and #1961, all closed, but with a trigger those did not cover: in-root relative import targets in an Astro plus TypeScript project (../../lib/content, ../../config, ../styles/global.css).
Because the importing file also produces a correctly named node for the same target, the result is a ghost duplicate: src_lib_content and users_<name>_..._<repo>_src_lib_content both exist in the graph.
Environment
- graphify 0.9.25 (installed via
uv tool, Python 3.14, macOS) - AST-only extraction (no LLM), undirected build
- Project: Astro site. Pages under
src/pages/**andsrc/layouts/Base.astroimport../../lib/content,../../config, and../styles/global.css.
Observed
Three import-target nodes carry an id derived from the absolute path, with a correct repo-relative source_file:
id: users_<name>_startups_<...>_<repo>_src_lib_content
source_file: src/lib/content
label: ../../lib/content
id: users_<name>_..._<repo>_src_config
source_file: src/config
id: users_<name>_..._<repo>_src_styles_global_css
source_file: src/styles/global.css
For src_lib_content and src_config, a correctly named node with the same repo-relative id already exists in the graph (the file's own node), so the absolute-id node is a pure duplicate. The absolute-id nodes are also the endpoints of imports_from edges, so on the post-build graph they show up as dangling endpoints (as in #1961).
Reproduction
Minimal shape: a file that imports another in-root file by a relative path that ascends at least one directory.
src/lib/content.ts
src/pages/work/index.astro -> import { ... } from '../../lib/content'
Then:
from pathlib import Path
from graphify.extract import extract
res = extract([Path('src/pages/work/index.astro'), Path('src/lib/content.ts')], cache_root=Path('.'))
print([n['id'] for n in res['nodes'] if 'lib_content' in n['id']])
# -> ['src_lib_content', 'users_<name>_..._src_lib_content'] # duplicate, second is absoluteNotes:
- Deterministic. It reproduces whether the paths passed to
extractare absolute or relative to the scan root. - It recurs through the post-commit hook.
graphify.watch._rebuild_code(root, changed_paths=[that page])regenerates the absolute-id node on every incremental rebuild of a file that has such an import, so a graph that was sanitized and committed gets re-poisoned the next time one of those code files changes.
Expected
The import-target node id should be relativized against the scan root before slugifying, the same way source_file already is, and should reuse the existing repo-relative id for the target file rather than minting a second node. When relative_to(root) cannot apply, fall back to the module specifier (the import string) rather than the absolute filesystem path.
Consequences
- The committed
graph.jsonleaks the local username and home directory layout (same as Node IDs for file / directory / project nodes embed the absolute scan path (leaks local username, makes committed graph.json non-portable / non-idempotent) #1789). - Ids differ per machine and per checkout location, so
graphify updateor the post-commit hook rebuild from another clone re-creates these nodes (diff churn, non-idempotent versioned graphs). - The duplicate/ghost nodes split what should be one node (see also bug: AST and semantic extractors generate different node IDs for the same symbol → god nodes duplicated, cluster cohesion degraded #1145 on duplicated god nodes) and appear as dangling endpoints post-build (AST extractor emits duplicate import edges with absolute-path node IDs (dangling endpoints) #1961).
Workaround in use
A pre-commit hook strips any node id that embeds an absolute path back to its repo-relative form and merges the duplicate into its canonical twin before commit, so the tracked graph stays clean. Happy to share the script if useful.
Activity
fix(extract): canonicalize regex-rescue import target ids (#2195)
safishamsi commentedon Jul 26, 2026
Fixed in v0.9.27. The Svelte/Astro/Vue regex-rescue import passes now resolve targets via the module resolver and stamp target_file, so import targets canonicalize to the root-relative file-node id instead of an absolute-path ghost. https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.27
Squashed commit of the following:
fix(extract): canonicalize regex-rescue import target ids (Graphify-L…
fix(extract): keep template composition edges alive through id disamb…
fix(extract): keep template composition edges alive through id disamb…