Skip to content

feat: Add configurable symlink following and depth limit for file watchers#4988

Open
takeokunn wants to merge 1 commit intomasterfrom
feature/improve-file-watch-1
Open

feat: Add configurable symlink following and depth limit for file watchers#4988
takeokunn wants to merge 1 commit intomasterfrom
feature/improve-file-watch-1

Conversation

@takeokunn
Copy link
Copy Markdown
Collaborator

@takeokunn takeokunn commented Feb 7, 2026

Summary

  • Add lsp-file-watch-follow-symlinks (nil / 'within-root / t) to control whether symlinks are followed during file watching. Default 'within-root follows symlinks only when the resolved target is inside the workspace root, preventing Emacs freezes caused by symlinks to large external directories (e.g., /usr/include).
  • Add lsp-file-watch-max-depth (default 100) to cap recursion depth during watcher setup, preventing stack overflow in deeply nested directory structures.
  • Refactor lsp--all-watchable-directories into a policy-dispatched function with cycle detection via a visited hash table keyed on truenames.
  • Fix runtime callback (lsp--folder-watch-callback) to resolve event paths to truenames before file enumeration, ensuring the within-root filter compares paths in the same namespace.
  • Store truename in lsp-watch root-directory at the registration site (lsp--server-register-capability), eliminating redundant re-resolution in the callback.
  • Replace destructive nconc with append in lsp--watchable-directories-recurse for long-term safety.

Breaking change

Symlinks pointing outside the workspace root are no longer followed by default. To restore previous behavior:

(setq lsp-file-watch-follow-symlinks t)

Additionally, lsp-watch-root-directory now always returns a truename-resolved path (previously it could return a raw path with unresolved symlink components).

Motivation

Implementation details

Callback path-type consistency

The runtime callback lsp--folder-watch-callback enumerates files in newly-created directories via directory-files-recursively. Under the within-root policy, these file paths are filtered using string-prefix-p against the workspace root.

Previously, directory-files-recursively was called on the raw event path (file-name), which could contain unresolved symlink segments. The filter compared these raw paths against a truename-resolved root — a namespace mismatch that could silently drop legitimate file events when the workspace was accessed through a symlink alias.

The fix resolves file-name to its truename before enumeration, ensuring all paths share the same resolved prefix.

Truename storage in lsp-watch

lsp--server-register-capability previously stored the raw folder path in lsp-watch :root-directory. The callback compensated by calling (file-truename (lsp-watch-root-directory watch)) on every invocation. Now the truename is stored at construction time, and the callback reads it directly.

Note: The created-watches hash table key remains the raw folder path (intentional — it must match folder->servers key convention for cl-set-difference and lsp--cleanup-hanging-watches).

Test plan

  • 24 passing tests covering all three symlink policies, depth limits (0, 2, nil), circular symlinks, dangling symlinks, root-as-symlink, file-truename error handling, runtime callback filtering, ignored directory interactions, symlink-alias event paths (inside and outside root), and lsp-watch-root-folder truename storage verification
  • Byte-compilation clean (0 errors, 0 new warnings)
  • 5 pre-existing test failures (confirmed same on master)
  • Performance: file-in-directory-p replaced with string-prefix-p on pre-resolved truenames

- Introduce lsp-file-watch-follow-symlinks to control symlink handling
- Add lsp-file-watch-max-depth to limit recursion during file watching
- Update docs and changelog for new symlink and depth options
- Change default: symlinks outside workspace root are not followed
- Extend and revise tests for symlink, depth, and error handling

BREAKING CHANGE: Symlinks pointing outside the workspace root are no
longer followed by default, preventing Emacs freezes. Set
lsp-file-watch-follow-symlinks to t to restore previous behavior.
@takeokunn takeokunn force-pushed the feature/improve-file-watch-1 branch from 75b03fd to 3c72282 Compare February 7, 2026 12:32
@takeokunn takeokunn marked this pull request as ready for review February 7, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lsp-watch-root-folder follows symlinks that point outside the root folder excessive-lisp-nesting

1 participant