fix: key File nodes on the link path so a deleted symlink is prunable (#1154) - #1155
Conversation
📝 WalkthroughWalkthroughThe change adds stable POSIX file identities that preserve symlink filenames. File nodes, containment relationships, contract indexing, and CONTRACT resources now use these identities. A regression test verifies identity persistence after symlink removal. ChangesSymlink identity handling
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
codebase_rag/tests/test_structure_processor.py (1)
413-418: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winVerify the containment target and post-unlink identity.
Add an assertion that the
CONTAINS_FILEtarget usesstored.
Callcached_file_identity_posix(link)afterlink.unlink()and assert that it equalsstored. This verifies both changed identity consumers.Proposed test additions
+ rel_calls = [ + c + for c in mock_ingestor.ensure_relationship_batch.call_args_list + if c[0][1] == "CONTAINS_FILE" + ] + assert len(rel_calls) == 1 + assert rel_calls[0][0][2] == ("File", "absolute_path", stored) + link.unlink() - assert link.resolve().as_posix() == stored + assert cached_file_identity_posix(link) == stored🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@codebase_rag/tests/test_structure_processor.py` around lines 413 - 418, Add assertions in the test around the symlink target to verify the CONTAINS_FILE target uses stored, then after link.unlink() call cached_file_identity_posix(link) and assert it equals stored. Preserve the existing resolve-based assertions while covering both identity consumers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@codebase_rag/tests/test_structure_processor.py`:
- Around line 413-418: Add assertions in the test around the symlink target to
verify the CONTAINS_FILE target uses stored, then after link.unlink() call
cached_file_identity_posix(link) and assert it equals stored. Preserve the
existing resolve-based assertions while covering both identity consumers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8f7f02ee-8c14-436d-96e5-69ae2f942ce8
⛔ Files ignored due to path filters (1)
assets/Screenshot 2026-08-09 at 21.01.57.pngis excluded by!**/*.png
📒 Files selected for processing (4)
codebase_rag/parsers/contract_linking.pycodebase_rag/parsers/structure_processor.pycodebase_rag/tests/test_structure_processor.pycodebase_rag/utils/path_utils.py
Greptile SummaryFile records now retain the in-repository symlink path as their stable identity, allowing incremental updates to match and remove records after a leaf symlink is deleted. Contract discovery and linking use the same identity, keeping contract relationships anchored to the corresponding File node. Confidence Score: 5/5No blocking failure remains; the change is safe to merge. A real in-repository symlink was processed through the structure and contract-linking paths, deleted, and then checked again. The File node, containment relationship, contract lookup, contract exposure relationship, and post-deletion identity all used the preserved link-path key.
What T-Rex did
Reviews (2): Last reviewed commit: "fix: key File nodes on the link path so ..." | Re-trigger Greptile |
|
|
@greptileai Thanks — the mechanism is real, but I think "not merge-safe" over-scopes it. Breaking it down: Full rebuild ( Incremental (
So this change is strictly better than the status quo, not a regression — it fixes the common case and makes new symlink nodes prunable. The legacy-migration cleanup for that narrow incremental-over-old-graph case is a separate concern, tracked in #1156 with a concrete approach. Please re-review with the |
chore: remove screenshot accidentally committed via #1155



Closes #1154.
Problem
File.absolute_pathwas computed at ingest viacached_resolve_posix()=Path.resolve().as_posix(), which dereferences a leaf symlink to its target. Two consequences for a symlinked file:resolve()can no longer follow it, so the delete key (CYPHER_DELETE_FILEmatchesabsolute_path) never matches the stored target path — theFilenode is left behind (both the realtime path andgraph_updater.py:1860).absolute_pathis insiderepo_path. A link pointing outside the repo was stored under the external target path, so it failed the containment gate and was never swept.Fix
Add
cached_file_identity_posix()— resolves only the parent (which outlives the leaf) and keeps the leaf name, so ingest and deletion agree on the key, and a link stays inside the repo it lives in (consistent with the GHSA-85gg containment stance). Applied to the soleFile-node creator (structure_processor.process_generic_file, node +CONTAINS_FILEedge) and to thecontract_linkingEXPOSESedge / index lookup that key on the sameFileidentity.Zero migration risk for regular files: when the leaf is not a symlink,
parent.resolve()/name==resolve(), so identities are byte-identical. Only symlink leaves change — from broken to correct.Scope
CYPHER_DELETE_MODULE), which is already symlink-safe.realtime_updater.py— that delete line is owned by the in-flight fix(realtime): delete files by absolute path #1153. This change is complementary: fix(realtime): delete files by absolute path #1153 fixes relative→absolute at delete time; this fixes the ingest identity so the two agree for symlinks.Test
test_symlinked_file_keyed_on_link_not_target(RED-verified): ingests a symlinked file, asserts theFilenode is keyed on the link’s own in-repo path (not the target), and thatlink.resolve()afterunlink()recovers exactly that key — proving ingest/delete agreement. Reverting the one-line ingest change fails the test.Summary by CodeRabbit