Skip to content

lsp diagnostics injection silently fails: mcpls returns object, zeph-core expects array #3593

@bug-ops

Description

@bug-ops

Description

LSP diagnostics context injection silently fails on every write tool call. The fetch_diagnostics function in zeph-core/src/lsp_hooks/diagnostics.rs expects mcpls get_diagnostics to return a bare JSON array ([{...}, ...]), but mcpls v0.3.6 returns a JSON object {"diagnostics": [{...}, ...]} (serialized DiagnosticsResult struct).

The result is that every diagnostics fetch silently returns None and no LSP context is ever injected into the prompt, even when lsp.diagnostics.enabled = true and mcpls is connected and available.

Reproduction Steps

  1. Configure [lsp] with enabled = true, mcp_server_id = "mcpls", and [lsp.diagnostics] enabled = true
  2. Add [[mcp.servers]] with id = "mcpls" command pointing to the mcpls binary
  3. Run the agent in full mode, ask it to write a Rust file
  4. Enable RUST_LOG=debug and observe the log

Expected Behavior

After write tool completes:

  • Background diagnostics task spawns (already works: "LSP hook: spawning diagnostics fetch")
  • get_diagnostics call completes and JSON is parsed as [{...}]
  • If diagnostics exist, [lsp diagnostics] System message is injected before the next LLM call

Actual Behavior

Debug log shows:

LSP diagnostics: failed to parse response JSON path="..." error=invalid type: map, expected a sequence at line 1 column 0

The parse fails silently, None is returned, no LSP context is injected.

Root Cause

diagnostics.rs:103:

let diagnostics: Vec<serde_json::Value> = match serde_json::from_str(json_text) {

mcpls get_diagnostics serializes DiagnosticsResult { diagnostics: Vec<Diagnostic> }, which produces:

{"diagnostics": [{...}]}

The parser expects a bare array [{...}].

This mismatch has existed since the initial LSP injection implementation — the expected format was never correct.

Fix

In crates/zeph-core/src/lsp_hooks/diagnostics.rs, replace the direct Vec parse with either:

  1. Parse as serde_json::Value, then extract .get("diagnostics") array (preferred — more resilient)
  2. Define a wrapper struct struct DiagnosticsResponse { diagnostics: Vec<serde_json::Value> } and parse into that

Environment

  • Version: 0.20.1 (HEAD 4e77b4f)
  • mcpls version: 0.3.6
  • Features: full (with [lsp] config)
  • Detected in: CI-661 live testing, 2026-05-05

Logs / Evidence

DEBUG zeph_core::lsp_hooks: LSP after_tool: checking availability tool="write"
DEBUG zeph_core::lsp_hooks: lsp_hooks: is_available check complete available=true
DEBUG zeph_core::lsp_hooks: LSP hook: spawning diagnostics fetch tool="write" path=.local/testing/data/lsp_test.rs
DEBUG zeph_core::lsp_hooks::diagnostics: LSP diagnostics: calling get_diagnostics path=".local/testing/data/lsp_test.rs" timeout_secs=10
DEBUG zeph_core::lsp_hooks::diagnostics: LSP diagnostics: failed to parse response JSON path=".local/testing/data/lsp_test.rs" error=invalid type: map, expected a sequence at line 1 column 0

Metadata

Metadata

Assignees

Labels

P2High value, medium complexitybugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions