Relationship to prior reports
This is the same symptom as #6077 (closed COMPLETED, March 2026), but a different trigger. #6077 covered duplicate diagnostics after a file is renamed to a different case, which was fixed in BC 22.0 via the DidChangeWatchedFiles handler. The closer of #6077 explicitly invited new issues for any residual duplicate-diagnostics problems.
This report is not a rename. The casing of the URI in textDocument/didOpen differs from the on-disk path from the start of the session, and the duplicate-declaration errors fire immediately. It still reproduces on AL 18.0.2190758.
Environment
- AL extension:
ms-dynamics-smb.al 18.0.2190758
- VS Code: 1.103.0 (run via
@vscode/test-electron in a deterministic harness; the same configuration repros 5 out of 5 runs)
- OS: Windows 11
Summary
When VS Code sends textDocument/didOpen with a file URI whose path casing differs from the on-disk casing (for example Cloud/Al/Activation/AppExperience.Codeunit.al vs the actual on-disk Cloud/AL/Activation/AppExperience.Codeunit.al), the AL Language Server treats the editor URI and its own filesystem-walked URI as two distinct files. Both produce a SingleObjectDeclaration for the same object. MergedNamespaceDeclaration.MakeChildren merges them under one MergedObjectDeclaration with two SyntaxReferences. SourceNamespaceSymbol.BuildObjectSymbolsFromDeclaration then creates two SourceCodeunitTypeSymbol entries, and SourceModuleSymbol.CheckForDuplicateIds correctly flags the duplicate. The user sees:
AL0197 An application object of type 'Codeunit' with name '<Name>'
is already declared by the extension '<own project name>'
AL0264 An application object of type 'Codeunit' with ID '<id>'
is already declared by the extension '<own project name>'
The "already declared by extension X" name in the message is the open project itself. Closing and reopening the offending file with its true on-disk casing makes the errors disappear.
Steps to reproduce
- AL workspace on Windows containing at least one object whose declaring file lives in a directory like
Cloud/AL/Activation/.
- Open the file via a URI that uses different casing for any path component, for example
Cloud/Al/Activation/AppExperience.Codeunit.al (lowercase l) instead of Cloud/AL/Activation/... (uppercase L). Windows resolves the path case-insensitively, so the file opens normally.
- Wait for the AL Language Server to publish diagnostics.
This can be triggered organically by:
- Multi-root workspaces where one root spells a child path with different casing than another.
- Extensions that issue
didOpen from cached or normalized paths (test runners, navigation aids, language server wrappers).
- Quick Open / workspace symbol search that surfaces paths from indexes whose casing has been folded.
Expected
The AL Language Server treats the two paths as the same file, parses it once, and produces one SingleObjectDeclaration. No AL0197 / AL0264 should fire.
Actual
The LS keeps two SyntaxTree instances (one from the FS scan during al/setActiveWorkspace, one from textDocument/didOpen) and processes them as separate declarations of the same object, producing AL0197 + AL0264 against the project itself.
Frame-level evidence
I built a stdio proxy that sits between VS Code and Microsoft.Dynamics.Nav.EditorServices.Host.exe and logs every JSON-RPC frame. The captured trace shows:
22:39:22.448 in al/setActiveWorkspace (no file list in params)
22:39:22.481 in textDocument/didOpen
uri = file:///.../Cloud/Al/Activation/AppExperience.Codeunit.al
^^ lowercase, as supplied by editor
22:39:22.514 in al/didChangeActiveDocument (same lowercase URI)
... (33 ms race window between the FS-scan trigger and the
didOpen for the same physical file) ...
In the eventual diagnostics, both casings appear:
- 13 diagnostics published under
Cloud/Al/Activation/AppExperience.Codeunit.al (the didOpen URI), including AL0264 + AL0197 at line 0.
- 169 diagnostics published under
Cloud/AL/Activation/... (the FS-scan URI), including the AL0185 errors expected for an unopened file in the same project.
al/setActiveWorkspace sends only the workspace folder path and AL settings, no file list. The duplicate-tree state therefore originates inside the LS itself when its workspace walk records files with on-disk casing while editor input arrives with caller casing.
Suspected root cause
SyntaxTree / SingleObjectDeclaration instances are keyed by the raw path string. On Windows the comparison should be case-insensitive (or paths should be canonicalised via GetFinalPathNameByHandle or equivalent) before the merge step in MergedNamespaceDeclaration.MakeChildren runs. Today they are not, so two strings that the OS treats as the same file become two distinct logical entities inside the compiler.
This may share underlying machinery with #6077 (the file-rename path that was fixed via DidChangeWatchedFiles). The fix from BC 22.0 normalises tracking after a rename event, but does not appear to canonicalise URIs at the point of textDocument/didOpen, leaving this static-casing-mismatch path open.
Suggested fix
On Windows, canonicalise file URIs to their on-disk casing (or compare them case-insensitively) before they reach the SyntaxTree cache and before declarations are grouped in MergedNamespaceDeclaration.MakeChildren. The most defensive option is to resolve every incoming textDocument/didOpen URI through GetFinalPathNameByHandle so the LS uses one canonical form everywhere.
Investigation notes and minimal reproducer
Curated summary, decompilation pointers, and the JSON-RPC frame trace are at:
https://github.com/SShadowS/al-lsp-for-agents/blob/main/docs/issue-17/README.md
Original report on my own tracker, with reproduction context from a Continia Core based BC project: SShadowS/al-lsp-for-agents#17
Internal work item: AB#634055
Relationship to prior reports
This is the same symptom as #6077 (closed COMPLETED, March 2026), but a different trigger. #6077 covered duplicate diagnostics after a file is renamed to a different case, which was fixed in BC 22.0 via the
DidChangeWatchedFileshandler. The closer of #6077 explicitly invited new issues for any residual duplicate-diagnostics problems.This report is not a rename. The casing of the URI in
textDocument/didOpendiffers from the on-disk path from the start of the session, and the duplicate-declaration errors fire immediately. It still reproduces on AL 18.0.2190758.Environment
ms-dynamics-smb.al18.0.2190758@vscode/test-electronin a deterministic harness; the same configuration repros 5 out of 5 runs)Summary
When VS Code sends
textDocument/didOpenwith a file URI whose path casing differs from the on-disk casing (for exampleCloud/Al/Activation/AppExperience.Codeunit.alvs the actual on-diskCloud/AL/Activation/AppExperience.Codeunit.al), the AL Language Server treats the editor URI and its own filesystem-walked URI as two distinct files. Both produce aSingleObjectDeclarationfor the same object.MergedNamespaceDeclaration.MakeChildrenmerges them under oneMergedObjectDeclarationwith twoSyntaxReferences.SourceNamespaceSymbol.BuildObjectSymbolsFromDeclarationthen creates twoSourceCodeunitTypeSymbolentries, andSourceModuleSymbol.CheckForDuplicateIdscorrectly flags the duplicate. The user sees:The "already declared by extension X" name in the message is the open project itself. Closing and reopening the offending file with its true on-disk casing makes the errors disappear.
Steps to reproduce
Cloud/AL/Activation/.Cloud/Al/Activation/AppExperience.Codeunit.al(lowercase l) instead ofCloud/AL/Activation/...(uppercase L). Windows resolves the path case-insensitively, so the file opens normally.This can be triggered organically by:
didOpenfrom cached or normalized paths (test runners, navigation aids, language server wrappers).Expected
The AL Language Server treats the two paths as the same file, parses it once, and produces one
SingleObjectDeclaration. No AL0197 / AL0264 should fire.Actual
The LS keeps two
SyntaxTreeinstances (one from the FS scan duringal/setActiveWorkspace, one fromtextDocument/didOpen) and processes them as separate declarations of the same object, producing AL0197 + AL0264 against the project itself.Frame-level evidence
I built a stdio proxy that sits between VS Code and
Microsoft.Dynamics.Nav.EditorServices.Host.exeand logs every JSON-RPC frame. The captured trace shows:In the eventual diagnostics, both casings appear:
Cloud/Al/Activation/AppExperience.Codeunit.al(the didOpen URI), including AL0264 + AL0197 at line 0.Cloud/AL/Activation/...(the FS-scan URI), including the AL0185 errors expected for an unopened file in the same project.al/setActiveWorkspacesends only the workspace folder path and AL settings, no file list. The duplicate-tree state therefore originates inside the LS itself when its workspace walk records files with on-disk casing while editor input arrives with caller casing.Suspected root cause
SyntaxTree/SingleObjectDeclarationinstances are keyed by the raw path string. On Windows the comparison should be case-insensitive (or paths should be canonicalised viaGetFinalPathNameByHandleor equivalent) before the merge step inMergedNamespaceDeclaration.MakeChildrenruns. Today they are not, so two strings that the OS treats as the same file become two distinct logical entities inside the compiler.This may share underlying machinery with #6077 (the file-rename path that was fixed via
DidChangeWatchedFiles). The fix from BC 22.0 normalises tracking after a rename event, but does not appear to canonicalise URIs at the point oftextDocument/didOpen, leaving this static-casing-mismatch path open.Suggested fix
On Windows, canonicalise file URIs to their on-disk casing (or compare them case-insensitively) before they reach the
SyntaxTreecache and before declarations are grouped inMergedNamespaceDeclaration.MakeChildren. The most defensive option is to resolve every incomingtextDocument/didOpenURI throughGetFinalPathNameByHandleso the LS uses one canonical form everywhere.Investigation notes and minimal reproducer
Curated summary, decompilation pointers, and the JSON-RPC frame trace are at:
https://github.com/SShadowS/al-lsp-for-agents/blob/main/docs/issue-17/README.md
Original report on my own tracker, with reproduction context from a Continia Core based BC project: SShadowS/al-lsp-for-agents#17
Internal work item: AB#634055