Skip to content

Watch the physical directory of files reached through a symlinked directory - #64449

Closed
Joaquín Sorianello (joac) wants to merge 2 commits into
microsoft:mainfrom
joac:fix/watch-symlinked-source-dirs
Closed

Joaquín Sorianello (joac) wants to merge 2 commits into
microsoft:mainfrom
joac:fix/watch-symlinked-source-dirs

Conversation

@joac

@joac Joaquín Sorianello (joac) commented Sep 25, 2026 •

Copy link
Copy Markdown

Fixes #64448

Problem

Since the file watcher stopped following symlinks during its directory walk (typescript-go#4376), tsc --watch never sees edits to program files that live behind a symlinked directory inside the project, e.g. src/common -> ../../common/src in monorepos that share sources this way. The recursive watch on src covers the logical path, but the OS subscription is on the physical src directory, which does not contain the symlink target. TypeScript 6.0.3 detected these edits; 7.0.2 and @next do not (repro in the issue).

Change (internal/execute/watcher.go)

  • In computeDesiredWatches, resolve the real path of each seen file's directory (memoised per directory). When it differs from the logical directory, watch the physical directory, non-recursively like other uncovered directories, and record the physical → logical mapping keyed by canonical path.
  • In DoCycle, right after draining events, rewrite events reported under such a physical directory back to the logical path. That is the name the program knows the file by, so the relevance check and the single-file fast path work unchanged.
  • Files whose program path is already real (e.g. node_modules resolved through realpath) take the existing code path; no behaviour change for them.

Tests

  • New tscWatch scenario "watch detects change in file under symlinked directory" with baseline. It shows the physical shared/src directory being watched and an edit behind the symlink producing a rebuild with the expected new error. With watcher.go stashed, the scenario fails.
  • Verified on a real filesystem (macOS 26, arm64) with the standalone repro from tsc --watch 7.x does not detect edits to files under a symlinked directory inside the project (6.0.3 did) #64448, using builds of this commit with and without the change: the unfixed build misses edits made through the real path and through the symlink, the fixed build detects both.

Notes for review

  • Cost: one Realpath per distinct seen-file directory on each reconcile after a full build, memoised per directory.
  • New entries behind the symlink are handled: creating a new file in the symlinked directory, or a new subdirectory (even two levels deep at once) with a file, raises an event on the watched physical directory. Mapped to the logical path it is a directory change under the wildcard root, which marks the file set dirty, so the next build re-enumerates the includes and picks the file up; the reconcile that follows adds a watch for the new directory, so later edits to that file are detected too (verified on macOS with the repro). The one remaining gap is a new file in a subdirectory that previously contained no program files, which has no watch of its own: it is picked up on the next build triggered by any other change. For comparison, TypeScript 6.0.3 detects edits to existing files behind the symlink but never notices new files or new subdirectories there, so this is strictly better than the previous behaviour.
  • Only macOS was exercised on a real filesystem; the in-memory test covers the logic for all backends.

Checklist

  • There is an associated issue: tsc --watch 7.x does not detect edits to files under a symlinked directory inside the project (6.0.3 did) #64448
  • Code is up-to-date with the main branch
  • npx hereby test: all internal/execute packages pass; the only failures in the full run are internal/fswatch/TestFSEventsExpansionAliases sub-cases (FSEvents Unicode-normalisation tests) which fail on this machine on main as well, with a different sub-case each run, and are untouched by this change
  • npx hereby lint (0 issues)
  • npx hereby check:format
  • There are new or updated tests validating the change

…ectory

Since the file watcher stopped following symlinks during its directory walk
(typescript-go#4376), `tsc --watch` never sees edits to program files that
live behind a symlinked directory inside the project, such as
`src/common -> ../../common/src` in monorepos that share sources this way.
The recursive watch on `src` covers the logical path, but the OS
subscription is on the physical `src` directory, which does not contain
the symlink target.

When computing desired watches, resolve the real path of each seen file's
directory. If it differs from the logical directory, watch the physical
directory (non-recursively, like other uncovered directories) and remember
the mapping. When draining events, rewrite paths under such a physical
directory back to the logical path, which is the name the program knows
the file by, so the existing relevance check and single-file fast path
work unchanged.

Fixes microsoft#64448.
Copilot AI balanced review requested due to automatic review settings September 25, 2026 18:50
@typescript-automation typescript-automation Bot added For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Sep 25, 2026
@joac

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The cross-platform filesystem watcher behavior and path remapping warrant final human review despite the focused regression coverage.

Review effort: Balanced
Findings: None

What changed in this PR

This PR restores watch-mode updates for source files reached through symlinked project directories by adding physical directory watches and translating their events back to logical program paths.

Changes:

  • Detects logical source directories whose real paths differ and watches their physical directories.
  • Remaps physical watcher events to logical paths before relevance and incremental-update checks.
  • Adds a tscWatch regression scenario and reference baseline.
File Description
tsc/​internal/​execute/​watcher.go Adds physical-directory watches and symlink event-path remapping.
tsc/​internal/​execute/​tsctests/​tscwatch_test.go Adds the symlinked-source watch regression scenario.
tsc/​testdata/​baselines/​reference/​tscWatch/​commandLineWatch/​watch-detects-change-in-file-under-symlinked-directory.js Captures expected watch registration and rebuild behavior.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@typescript-automation typescript-automation Bot added For Milestone Bug PRs that fix a bug with a specific milestone and removed For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Sep 25, 2026
@joac

Copy link
Copy Markdown
Author

This was already fixed on #64404

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Milestone Bug PRs that fix a bug with a specific milestone

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

tsc --watch 7.x does not detect edits to files under a symlinked directory inside the project (6.0.3 did)

3 participants