Repository navigation
Workspace enumeration follows symlinks out of the workspace: wine prefix dosdevices/z: -> / pins a CPU core at 100% and floods the log #5620
Description
Type: Performance Issue
Summary
The PowerShell language server (PSES) recursively enumerates the whole workspace on startup looking for .ps1/.psm1/.psd1 files, and follows directory symlinks with no containment or cycle check. A single symlink pointing to the filesystem root — e.g. a standard Wine prefix inside the workspace, which always contains dosdevices/z: -> / — makes the walk effectively unbounded: PSES pins one CPU core at ~100% indefinitely and floods its log (~5 MB every 1–2 minutes) with Could not enumerate directories in the path '…' due to the path not being accessible warnings. Removing the symlink is not enough by itself — the already running enumeration never finishes; the session has to be restarted.
Environment
- Extension version: 2025.4.0
- VS Code version: 1.140.0
- OS version: macOS 26.7.1, arm64
- PowerShell version: 7.6.6
Repro
- Put a standard Wine prefix inside the workspace (any folder where one runs/tests Windows tools; it does not even need to be tracked by git — the enumerator does not consult
.gitignore):WINEPREFIX=<workspace>/Temp/wine-prefix wineboot --init ls -l <workspace>/Temp/wine-prefix/dosdevices # c: -> ../drive_c # z: -> / <-- auto-created by wine for every prefix - Open the workspace in VS Code with the PowerShell extension and open any
.ps1file.
Observed
- The
pwsh(PSES) process sits at ~100% of one core for hours (verified over 1.5 h; it had accumulated 72 minutes of CPU time by the time I sampled it). - The PSES log rotates every 1–2 minutes at ~5 MB per file, almost entirely entries like:
The logged paths show the walker descending into
<Warning>Microsoft.PowerShell.EditorServices.Services.WorkspaceService: Could not enumerate directories in the path '/Users/…/Temp/wine-prefix/dosdevices/z:/…' due to the path not being accessiblez:and re-entering the workspace through nestedz:segments. sample <pid>shows a single .NET thread-pool worker spending ~100% of its samples in filesystem enumeration syscalls:SystemNative_OpenDir/SystemNative_ReadDir/SystemNative_Stat/SystemNative_LStat.
Root cause
WorkspaceService.FollowSymlinksdefaults totrue, soEnumeratePSFiles()runs withignoreReparsePoints: falseandmaxDepth: 64: WorkspaceService.cs.WorkspaceFileSystemWrapper.SafeEnumerateFileSystemInfosapplies only thatMaxRecursionDepthcap — the mitigation added in PowerShellEditorServices#795 for Process terminated bue to StackOverFlow - process terminated with code 3221225725 #1613: WorkspaceFileSystemWrapper.cs. A depth cap bounds nesting, but not breadth or revisits: az: -> /link lets the walker enumerate the entire filesystem, and since/contains the workspace itself, the walk re-entersdosdevices/z:again and again up to the depth limit — in practice unbounded work on a real disk.
Workaround
rm "<workspace>/Temp/wine-prefix/dosdevices/z:"
then restart the session (PowerShell: Restart Current Session or reload the window). Wine itself works fine without the Z: drive, so the symlink can safely be deleted right after prefix creation (and re-deleted whenever the prefix is re-initialized).
Possible directions
- Don't follow directory symlinks whose resolved target is outside the workspace roots (containment check), and/or
- detect cycles (e.g. a visited (device, inode) set for symlinked directories), and/or
- default
FollowSymlinkstofalse, or expose it as a user setting, - optionally consult
.gitignore— in my case the indexer burned a core over aTemp/folder that git itself ignores.
Happy to test a build with any of these.
Activity