Skip to content

fix(lsp): inject hover notes immediately before LLM call (#3595)#3596

Merged
bug-ops merged 3 commits intomainfrom
3595-lsp-hover-injection
May 5, 2026
Merged

fix(lsp): inject hover notes immediately before LLM call (#3595)#3596
bug-ops merged 3 commits intomainfrom
3595-lsp-hover-injection

Conversation

@bug-ops
Copy link
Copy Markdown
Owner

@bug-ops bug-ops commented May 5, 2026

Summary

  • LSP hover/diagnostic notes collected in LspHookRunner::after_tool were never reaching the LLM because the injection guard !last_msg_has_tool_results() always returned false after the first tool loop iteration
  • Moved the injection block from process_single_native_turn into call_chat_with_tools, immediately after run_before_chat_layers and before provider.chat_with_tools — at that point no ToolUse/ToolResult pair is pending, so inserting a Role::System message is safe for all providers
  • Removed the now-unused last_msg_has_tool_results function and its 5 unit tests

Root cause

The original code placed LSP note injection at the top of each iteration of the tool loop. The guard prevented injection when the last non-System message was User(ToolResult). However, after iteration N completes, the committed tool results from that iteration make last_msg_has_tool_results() return true at the start of iteration N+1 — so notes were always discarded.

Test plan

  • cargo build -p zeph-core — clean
  • cargo clippy -p zeph-core -- -D warnings — zero warnings
  • cargo +nightly fmt --check — clean
  • cargo nextest run --workspace --lib --bins — 8835 passed, 0 failed
  • Live test: use read tool on a Rust file with LSP enabled, verify hover notes appear in the next LLM context (requires mcpls + LSP server)

Closes #3595

The previous implementation attempted to inject LSP notes at the top of
each tool loop iteration, guarded by `!last_msg_has_tool_results()`. This
guard correctly returned `true` on every iteration after the first because
the previous iteration's `User(ToolResult)` message was always the last
non-System message — blocking injection permanently.

Move the injection block into `call_chat_with_tools`, after
`run_before_chat_layers` and immediately before `provider.chat_with_tools`.
At that point there is no pending ToolUse/ToolResult pair, so inserting a
`Role::System` message is safe for all providers (OpenAI, Claude, Ollama).

Remove the now-unused `last_msg_has_tool_results` function and its tests.

Closes #3595
@github-actions github-actions Bot added bug Something isn't working rust Rust code changes core zeph-core crate size/M Medium PR (51-200 lines) and removed bug Something isn't working labels May 5, 2026
Add two tests covering the new call_chat_with_tools injection path:
- lsp_notes_injected_before_llm_call_in_call_chat_with_tools: verifies
  that queued hover notes appear as a System message before the LLM call
- lsp_notes_not_duplicated_on_retry: verifies that on ContextLengthExceeded
  retry the note is not re-injected (drain returns None, stale msg removed)

Add #[cfg(test)] push_note helper to LspHookRunner for pre-queuing
notes without an MCP connection.

Fix formatting.
@github-actions github-actions Bot added bug Something isn't working size/L Large PR (201-500 lines) and removed size/M Medium PR (51-200 lines) labels May 5, 2026
@bug-ops bug-ops enabled auto-merge (squash) May 5, 2026 12:21
@bug-ops bug-ops merged commit 7f714b4 into main May 5, 2026
32 checks passed
@bug-ops bug-ops deleted the 3595-lsp-hover-injection branch May 5, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working core zeph-core crate rust Rust code changes size/L Large PR (201-500 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lsp hover notes never reach LLM: last_msg_has_tool_results always blocks injection

1 participant