microsoft / microsoft/monaco-editor
[Bug] [LSP] `textDocument/codeAction` request omits diagnostic fields the server needs to produce quickfixes (`data`, `code`, `source`, …)
@hediet is already working on this.
Since Jun 5, 2026.
- Dominant language
- JavaScript
- Stars
- 46.8k
- Forks
- 4.1k
- Avg merge
- 17h 58m
- Merged PRs (30d)
- 1
Description
Reproducible in vscode.dev or in VS Code Desktop?
- Not reproducible in vscode.dev or VS Code Desktop
Reproducible in the monaco editor playground?
- Not reproducible in the monaco editor playground
Monaco Editor Playground Link
Summary
LspCodeActionProvider.provideCodeActions constructs context.diagnostics for the outgoing textDocument/codeAction request from context.markers, but forwards only range, message, and severity. LSP diagnostic fields that servers commonly key off — code, source, tags, relatedInformation, codeDescription, and especially data (the server-roundtripped opaque payload) — are dropped, even though most are present on IMarker and the data / codeDescription payloads are still available on the original Diagnostic objects this client just received from publishDiagnostics / textDocument/diagnostic.
Effect
For any server that uses Diagnostic.data to carry quickfix metadata (the LSP 3.16+ mechanism for diagnostic-targeted code actions, gated on dataSupport: true — which this client already advertises in LspDiagnosticsFeature.ts:25), the server can't identify the diagnostic in the codeAction request and returns no quickfix.* actions. The Monaco lightbulb only shows source-level actions (source.fixAll.*, source.organizeImports.*), and the user-facing "Remove unused import" / "Add missing return" / etc. quickfixes never appear.
Concrete real-world example: ruff server attaches data to every diagnostic. Without it ruff returns zero quickfix actions, even though the same server emits all of them when a fully-formed diagnostic is round-tripped back.
Reproduction (3-shape probe against ruff)
Initialize → didOpen ("import os\n\nprint('hi')\n" → F401 unused import) → wait for publishDiagnostics. Then send three textDocument/codeAction requests with different context.diagnostics:
| Shape | context.diagnostics payload |
Result |
|---|---|---|
| A — full Diagnostic as ruff sent it | [{ range, message, severity, code: "F401", source: "Ruff", data: {…} }] |
4 actions, including 2 quickfix.* ("Remove unused import: 'os'", "Disable for this line") |
B — stripped to range/message/severity (what LspCodeActionProvider sends today) |
[{ range, message, severity }] |
2 source-only actions, no quickfixes |
| C — empty | [] |
2 source-only actions, no quickfixes |
Shape B is what every Monaco user sees today. range + message alone is theoretically enough to route the request, but in practice servers like ruff key off data and silently fall back to source actions.
Root cause
monaco-lsp-client/src/adapters/languageFeatures/LspCodeActionFeature.ts:80-85:
diagnostics: context.markers.map(marker => ({
range: this._client.bridge.translateRange(model, monaco.Range.lift(marker)),
message: marker.message,
severity: toLspDiagnosticSeverity(marker.severity),
})),
code, source, tags, and relatedInformation are recoverable directly from marker — they're already preserved by toDiagnosticMarker in common.ts:372-399 when LSP diagnostics are turned into IMarkers. But data and codeDescription cannot survive the round-trip through IMarker because Monaco's marker model doesn't have fields for them. They're available on the original Diagnostic objects in LspDiagnosticsFeature, but that feature doesn't expose them.
Proposed fix
- New
DiagnosticsCache(URI →Diagnostic[]) populated byLspDiagnosticsFeaturewhenever it receives diagnostics (push and pull paths), cleaned onmodel.onWillDispose. - Expose it via
LspConnectionso any feature can read it. LspCodeActionProvider.provideCodeActionslooks up each marker's matchingDiagnostic(by URI + range + message) and forwards the original object verbatim. Markers without a cache hit (e.g. set by an unrelated owner) fall back to a synthesized Diagnostic that still passes throughcode,source,tags, andrelatedInformationfromIMarker— so foreign markers still contribute the fields they have.
Diff in the linked PR.
LSP 3.17 spec reference
Per § Diagnostic:
A data entry field that is preserved between a
textDocument/publishDiagnosticsnotification andtextDocument/codeActionrequest. […] Servers are allowed to attach information to a diagnostic, e.g. for purposes of identifying it later.
The current implementation makes this round-tripping impossible.
Monaco Editor Playground Code
Reproduction Steps
No response
Actual (Problematic) Behavior
No response
Expected Behavior
No response
Additional Context
No response
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.
Assessment
This issue has not been assessed yet.