Debug adapter falls back to process CWD as package cache and recursively scans it — on Linux (CWD = $HOME) attach takes 15-30s and stackTrace never responds
Nobody has claimed this yet.
- Dominant language
- PowerShell
- Stars
- 881
- Forks
- 285
- Avg merge
- 3d 36m
- Merged PRs (30d)
- 1
Description
Describe the issue
On Linux, AL debugging (both attach and launch) against an OnPrem server is effectively broken in real VS Code, while the very same debug adapter binary works fine when driven headlessly. Symptoms:
attachtakes 15ΓÇô30 seconds to complete (instead of <1 s).- When a breakpoint hits, VS Code shows "Paused on Step" with an empty call stack that never populates ΓÇö the DAP
stackTracerequest gets no response (observed >3 minutes with none). DebuggerServices.logfills with:
An exception happened while reading the package cache: '/home/<user>'. Too many levels of symbolic links : '/home/<user>/...'
Root cause
Microsoft.Dynamics.Nav.EditorServices.Protocol.dll, SettingsExtensions.GetPackageCachePaths:
string[] array = settings.AlResourceConfigurationSettings?.PackageCachePaths;
if (array == null || array.Length == 0)
{
array = new string[1] { Directory.GetCurrentDirectory() }; // <-- fallback
}
The debug adapter is a separate process from the language server and never receives al.packageCachePath: no CLI argument carries it (getLanguageServerOptions in the extension does not emit it) and the extension never sends al/setActiveWorkspace over the DAP channel. So during a debug session this fallback always fires.
The extension spawns the adapter via new DebugAdapterExecutable(path, args) without a cwd, so the adapter inherits the VS Code extension host's working directory. On Windows that happens to be a small directory (e.g. the VS Code install dir), so the recursive *.app scan triggered by the first stackTrace-induced compilation (DebugAdapterStackTraceHandler → Workspace.CurrentSolution.GetCompilationAsync → MovedSymbolsPackageCacheLoader.LoadMovedObjectsManifests) and during attach processing finishes instantly and the bug stays invisible. On Linux the extension host CWD is typically the user's home directory: the scan walks hundreds of thousands of files, and any symlink cycle under $HOME (e.g. tooling like mise creates ~/.local/state/mise/trusted-configs/<id> -> /home/<user>) makes it never terminate — each pass dies with "Too many levels of symbolic links" and is retried.
Steps to reproduce
- Linux, AL extension 17.0.2273547 (EditorServices Host v17.0.34.45391), any OnPrem launch config with
"request": "attach", project withoutal.packageCachePathset. - Launch VS Code so its CWD is
$HOME(the default for desktop launchers andcodefrom~). - Start the attach config, sign in to the web client, hit a breakpoint.
- Observe slow attach, then "Paused on Step" with a never-loading call stack; see the package-cache exceptions in
DebuggerServices.log.
Control experiment: spawning the identical adapter binary with the project folder as CWD makes attach complete in ~1 s and stackTrace respond in ~3 s ΓÇö confirming the CWD fallback is the trigger.
| Adapter CWD | attach |
first stackTrace |
|---|---|---|
| project folder | <1 s | ~3 s |
$HOME (what VS Code uses on Linux) |
15ΓÇô30 s | no response in >3 min |
How to fix ΓÇö detailed guidance
Four layers; (1) alone fixes the issue from the extension side, (2)ΓÇô(4) fix it properly in the adapter.
1. Extension (TypeScript) ΓÇö pass cwd when spawning the adapter. In AlDebugAdapterDescriptorFactory.createDescriptor, the workspace folder is already in scope (it is used for /projectRoot:). Add the DebugAdapterExecutableOptions third argument:
// before
return new vscode.DebugAdapterExecutable(serverPath, args);
// after
return new vscode.DebugAdapterExecutable(serverPath, args, { cwd: workspaceFolder.uri.fsPath });
Verified locally against the 17.0.2273547 bundle (patching the equivalent minified call in dist/extension.js): attach goes from ~25 s to ~1 s, stackTrace responds in ~3 s, and F5 launch mode becomes reliable too ΓÇö before the fix, the multi-second scan during attach/launch processing raced the browser's session creation, so the debuggingcontext session often failed to bind (sign-in loop, debugger never bound).
2. Adapter (C#) ΓÇö fix the fallback itself. In SettingsExtensions.GetPackageCachePaths, the method already early-returns when settings.WorkspacePath is empty, so a workspace path is guaranteed at the fallback site. Default to the documented default of al.packageCachePath (./.alpackages relative to the project) instead of the process CWD:
// before
array = new string[1] { Directory.GetCurrentDirectory() };
// after
array = new string[1] { ".alpackages" }; // resolved against WorkspacePath by the Select below
(The method already maps every entry through PathUtilities.CombinePath(settings.WorkspacePath.GetFilePath(), path), which handles the relative path.) Directory.GetCurrentDirectory() is never a meaningful location for a DAP process spawned by an IDE.
3. Transport ΓÇö actually deliver al.packageCachePath to the debug adapter. Two mechanisms already exist and just aren't wired up:
- CLI:
getLanguageServerOptions()already serializes comparable settings (e.g.al.testCoverageCachePath→/testCoverageCachePath:); emit/packageCachePath:the same way and populateSettings.AlResourceConfigurationSettings.PackageCachePathsfrom it in the host. - DAP channel:
SetActiveWorkspaceRequestHandler(al/setActiveWorkspace) is registered byChannelBase, so the debug adapter can already receive full workspace settings ΓÇö the extension simply never sends that request on debug sessions. Sending it right afterinitialize(beforeattach/launch) would populate the settings ahead of the first compilation.
4. Hardening ΓÇö make the scan cycle-safe regardless of root. The recursive *.app enumeration reached from MovedSymbolsPackageCacheLoader.LoadMovedObjectsManifests and ReferenceLoaderFactory.CreateReferenceLoader (via IFileSystem.GetFiles(cachePath, "*.app")) should enumerate with:
new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true,
AttributesToSkip = FileAttributes.ReparsePoint, // do not follow symlinked dirs -> no cycles
}
Today a symlink cycle surfaces as IOException ("Too many levels of symbolic links"), the pass is abandoned and retried, and the operation never completes. Also consider skipping the scan entirely when the cache directory does not exist.
Possibly related: #8153
#8153 (AL debugger cannot open files in dev containers / remote sessions — stack frame paths are remote-side paths the local window cannot open) lives in the same code path this issue is about: DebugAdapterStackTraceHandler.ToStackFrameAsync / CreateSourceAsync put raw machine-local filesystem paths into DAP Source.path — location.GetLineSpan().Path for in-project frames, and FilePathHelper.GetPreviewFileUri(project.ProjectFolder, …) with Origin = "External" for out-of-project frames. Whether a frame resolves to a real source location at all also depends on the same project compilation whose package-cache root is broken here. The two are distinct defects, but both come from the DAP host making process-local path/environment assumptions (CWD, raw fs paths) that only hold on a non-remote Windows desktop — a combined fix pass over that area would address both.
Versions
- AL extension: 17.0.2273547 (EditorServices Host v17.0.34.45391)
- VS Code: stable, Linux x64
- OS: Arch Linux (reproduced generally: any Linux where the extension host CWD is
$HOME) - Server: BC 28.1 OnPrem
Internal work item: AB#638489
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with AlDebugAdapterDescriptorFactory.createDescriptor and getLanguageServerOptions in the extension, then inspect SettingsExtensions.GetPackageCachePaths and the package-cache enumeration reached through MovedSymbolsPackageCacheLoader.LoadMovedObjectsManifests. Reproduce with the adapter CWD set to a workspace and to $HOME; done means debug sessions use the workspace cache rather than process CWD and recursive scans do not follow symlink cycles.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, typescript, vscode
- Domain
- developer-experience, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 50/100