#23620: fix(memory): follow symlinks in memory indexer, reader, and watcher
cli
size: S
Cluster:
Browser Security Enhancements
Closes #23619
## Summary
- Memory indexer, reader, and file watcher use `lstat` + `isSymbolicLink()` to explicitly reject symlinked files and directories. This silently breaks use cases where workspace memory files are symlinked to an external location (e.g., an Obsidian vault).
- Changed `lstat` → `stat` and removed `isSymbolicLink()` rejection logic in 4 files so symlinks are followed transparently.
- `walkDir` now resolves symlink targets via `fs.stat()` to determine if they're files or directories before recursing/collecting.
## Safety
- Circular symlinks: `fs.stat()` throws `ELOOP`, caught by existing `try/catch` blocks
- Duplicate indexing: already handled by `realpath()`-based deduplication in `listMemoryFiles()`
## Test plan
- [ ] Create a symlinked `.md` file in `memory/` → verify it appears in `memory_search` results
- [ ] Create a symlinked `memory/` directory → verify files inside are indexed
- [ ] Symlinked `MEMORY.md` at workspace root → verify it loads at bootstrap and is indexed
- [ ] Symlinked `extraPaths` entry → verify it is indexed and watched
- [ ] `memory_get` on a symlinked file → verify it returns content instead of throwing
- [ ] Circular symlink in `memory/` → verify no crash (silently skipped)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Changed memory system from rejecting symlinks (`lstat` + `isSymbolicLink()` checks) to following them transparently (`stat`), enabling workspace memory files stored in external locations like Obsidian vaults.
**Key changes:**
- Replaced `fs.lstat()` with `fs.stat()` in 4 files to dereference symlinks
- Removed explicit `isSymbolicLink()` rejection logic
- `walkDir` now recursively follows symlinked directories and collects symlinked `.md` files
- Circular symlinks protected by existing error handling (`fs.stat()` throws `ELOOP`, caught silently)
- Duplicate indexing handled by existing `realpath()`-based deduplication
**Issues found:**
- `walkDir` checks `entry.name.endsWith(".md")` for symlinks instead of checking the resolved target path
<h3>Confidence Score: 4/5</h3>
- Safe to merge with minor fix - symlink following logic is sound with good safety measures
- The PR successfully enables symlink following with proper circular symlink protection and deduplication. One logical bug in `walkDir` affects edge cases where symlink names don't match target filenames. Existing error handling and deduplication provide good safety. The changes are well-isolated to memory file operations.
- src/memory/internal.ts needs the filename check fix for symlinks
<sub>Last reviewed commit: 7d16ab2</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#2884: fix: Create memory directory and symlink identity files during work...
by webdevtodayjason · 2026-01-27
76.2%
#3564: fix(plugins): follow symlinks in plugin and hook discovery
by mollywires · 2026-01-28
73.2%
#21245: fix(plugins): harden symlinked extension discovery
by victorGPT · 2026-02-19
72.2%
#22910: fix(browser): resolve symlinks in upload path validation
by erdinccurebal · 2026-02-21
71.3%
#7007: Fix security audit false-positive for symlinked state dir
by MohammadErfan-Jabbari · 2026-02-02
71.3%
#6362: fix(memory): add ignore patterns to chokidar file watcher
by Glucksberg · 2026-02-01
70.6%
#9154: fix(doctor): resolve symlinks before comparing state directories
by gavinbmoore · 2026-02-04
69.9%
#4386: fix(memory): persist dirty flag to prevent false positive on status
by Iamadig · 2026-01-30
69.7%
#18593: fix: resolve symlinks in session path validation (#18553)
by EpaL · 2026-02-16
69.0%
#23308: fix(browser): accept upload paths that traverse symlinked tmp dirs
by SidQin-cyber · 2026-02-22
68.8%