What happened?
NativeLspService fans every LSP query out over the servers that match the file and collects whatever
hits come back. Each per-server attempt is wrapped in its own try/catch, the catch only writes a
debug log, and the method then falls through to return []. An empty array is also what a successful
query with no hits returns, so callers cannot tell the two apart. When no server can answer — every
attempt threw — the caller is told "no results", and the user sees an empty result instead of a failure.
This is the layer that turned the defect in #12206 into a silent No document symbols found rather than
a visible error: the framing bug made every response unparseable, every request eventually raised, every
catch logged a warning, and documentSymbols returned [] — the same value it returns for a file
that genuinely has no symbols.
Affected methods at main @ 537311b8 (packages/core/src/lsp/native-lsp-service.ts), all sharing the
same shape (per-server catch → debugLogger.warn → fall through to return []):
| method |
start |
catch |
return [] |
workspaceSymbols |
979 |
1102 |
1110 |
definitions |
1048 |
1170 |
1178 |
documentSymbols |
1234 |
1300 |
1308 |
implementations |
1314 |
1371 |
1379 |
incomingCalls |
1626 |
1671 |
1682 |
outgoingCalls |
1688 |
1733 |
1744 |
workspaceDiagnostics |
1802 |
1965 |
1973 |
documentSymbols, :1295-1308:
if (symbols.length > 0) {
return symbols.slice(0, limit);
}
} catch (error) {
debugLogger.warn(
`LSP textDocument/documentSymbol failed for ${name}:`,
error,
);
}
}
return [];
}
The single-server, no-hit and all-servers-failed paths therefore converge on one return value, and the
diagnostic goes to debugLogger.warn, which is not part of the user-facing surface. Nothing above this
layer can recover the distinction: the returned [] carries no failure information at all.
What did you expect to happen?
"No results" and "could not ask" should be distinguishable. A query whose servers all failed should
surface as an error (or as a result type that carries the failures), while [] keeps its current meaning
of "at least one server answered, and it found nothing". For the query methods the difference is
usability; for workspaceDiagnostics it is stronger, because [] there asserts "no problems in this
codebase" — a claim that is currently also made when every server errored.
Client information
- qwen-code version: 0.24.0 (read from
/root/.local/lib/qwen-code/package.json)
- install method: local install under
/root/.local — bin wrapper /root/.local/bin/qwen, entry
scripts/cli-entry.js; the shipped build is an esbuild bundle under lib/chunks
- node:
v24.18.0
- uname -a:
Linux FX506LI 6.17.0-35-generic #35~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 19:30:42 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
This is the headless equivalent of /about; the interactive command could not be run here.
Root cause
There is no record of whether any server attempt succeeded. The per-server loop accumulates hits and
breaks out early when it has enough, but on the failure path it only logs, so the method reaches its
final return [] with no way to know that the loop ended because everything threw. The try also wraps
the whole per-server body, so an error raised anywhere inside the attempt — connection failure, request
timeout, malformed response, a rejected promise from the server — lands in the same catch and is
reduced to the same [].
Proposed direction
Decide the all-failed case explicitly rather than letting it fall through: track whether any attempt
completed, and return [] only when at least one server actually answered (with no hits). If every
attempt threw, surface the collected errors — either by throwing, or by widening the return type so the
caller can report "the LSP servers failed" instead of "nothing found". The per-server warn can stay for
the partial-failure case; the change is specifically about the case where nothing succeeded.
This is offered as a direction, not a patch: it changes a return contract that several callers and the
tool layer consume, so the shape is a maintainer call. It was raised here rather than folded into #12206
because that fix is deliberately scoped to the framing defect (PR #12210), as noted in that thread.
Verification
Static source review only — not reproduced. Everything above was read from
packages/core/src/lsp/native-lsp-service.ts at main @ 537311b8, fetched with
gh api "repos/QwenLM/qwen-code/contents/packages/core/src/lsp/native-lsp-service.ts?ref=main". No
executed reproduction of the all-servers-failed path is included, so the claim rests on the code shape
above rather than on observed output. What I did not do: instantiate NativeLspService against a server
stubbed to fail, which is what would show the user-visible empty result end to end.
The finding is corroborated independently: the maintainer analysis in #12206
(#12206) reached the same conclusion while scoping the framing
fix — "Deliberately left out of scope: native-lsp-service.ts collapsing a per-server request failure
into an indistinguishable [], which is what turned this into 'No document symbols found' instead of a
visible error. That is a separate design question — worth its own issue if you want it tracked." This
issue is that follow-up, filed at that invitation.
Related, not duplicate: #3649 (closed) exposed LSP status and startup diagnostics; this report is about
per-request failure being reported as a successful empty result.
What happened?
NativeLspServicefans every LSP query out over the servers that match the file and collects whateverhits come back. Each per-server attempt is wrapped in its own
try/catch, thecatchonly writes adebug log, and the method then falls through to
return []. An empty array is also what a successfulquery with no hits returns, so callers cannot tell the two apart. When no server can answer — every
attempt threw — the caller is told "no results", and the user sees an empty result instead of a failure.
This is the layer that turned the defect in #12206 into a silent
No document symbols foundrather thana visible error: the framing bug made every response unparseable, every request eventually raised, every
catchlogged a warning, anddocumentSymbolsreturned[]— the same value it returns for a filethat genuinely has no symbols.
Affected methods at
main@537311b8(packages/core/src/lsp/native-lsp-service.ts), all sharing thesame shape (per-server
catch→debugLogger.warn→ fall through toreturn []):catchreturn []workspaceSymbolsdefinitionsdocumentSymbolsimplementationsincomingCallsoutgoingCallsworkspaceDiagnosticsdocumentSymbols,:1295-1308:The single-server, no-hit and all-servers-failed paths therefore converge on one return value, and the
diagnostic goes to
debugLogger.warn, which is not part of the user-facing surface. Nothing above thislayer can recover the distinction: the returned
[]carries no failure information at all.What did you expect to happen?
"No results" and "could not ask" should be distinguishable. A query whose servers all failed should
surface as an error (or as a result type that carries the failures), while
[]keeps its current meaningof "at least one server answered, and it found nothing". For the query methods the difference is
usability; for
workspaceDiagnosticsit is stronger, because[]there asserts "no problems in thiscodebase" — a claim that is currently also made when every server errored.
Client information
/root/.local/lib/qwen-code/package.json)/root/.local— bin wrapper/root/.local/bin/qwen, entryscripts/cli-entry.js; the shipped build is an esbuild bundle underlib/chunksv24.18.0Linux FX506LI 6.17.0-35-generic #35~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 19:30:42 UTC 2 x86_64 x86_64 x86_64 GNU/LinuxThis is the headless equivalent of
/about; the interactive command could not be run here.Root cause
There is no record of whether any server attempt succeeded. The per-server loop accumulates hits and
breaks out early when it has enough, but on the failure path it only logs, so the method reaches its
final
return []with no way to know that the loop ended because everything threw. Thetryalso wrapsthe whole per-server body, so an error raised anywhere inside the attempt — connection failure, request
timeout, malformed response, a rejected promise from the server — lands in the same
catchand isreduced to the same
[].Proposed direction
Decide the all-failed case explicitly rather than letting it fall through: track whether any attempt
completed, and return
[]only when at least one server actually answered (with no hits). If everyattempt threw, surface the collected errors — either by throwing, or by widening the return type so the
caller can report "the LSP servers failed" instead of "nothing found". The per-server
warncan stay forthe partial-failure case; the change is specifically about the case where nothing succeeded.
This is offered as a direction, not a patch: it changes a return contract that several callers and the
tool layer consume, so the shape is a maintainer call. It was raised here rather than folded into #12206
because that fix is deliberately scoped to the framing defect (PR #12210), as noted in that thread.
Verification
Static source review only — not reproduced. Everything above was read from
packages/core/src/lsp/native-lsp-service.tsatmain@537311b8, fetched withgh api "repos/QwenLM/qwen-code/contents/packages/core/src/lsp/native-lsp-service.ts?ref=main". Noexecuted reproduction of the all-servers-failed path is included, so the claim rests on the code shape
above rather than on observed output. What I did not do: instantiate
NativeLspServiceagainst a serverstubbed to fail, which is what would show the user-visible empty result end to end.
The finding is corroborated independently: the maintainer analysis in #12206
(#12206) reached the same conclusion while scoping the framing
fix — "Deliberately left out of scope:
native-lsp-service.tscollapsing a per-server request failureinto an indistinguishable
[], which is what turned this into 'No document symbols found' instead of avisible error. That is a separate design question — worth its own issue if you want it tracked." This
issue is that follow-up, filed at that invitation.
Related, not duplicate: #3649 (closed) exposed LSP status and startup diagnostics; this report is about
per-request failure being reported as a successful empty result.