Evidence CLI crashes with ruby-lsp InvalidLocationError on a references lookup #1284

Closed
opened 2026-10-23 20:14:54 +00:00 by jared · 2 comments
Owner

Problem

os-sdlc implementation 144 (#1280) failed at its first dispatch. The change-auditor (dispatch 1224) ran 400 s, wrote a complete work list, and returned verdict error because the evidence CLI crashed. One crash in a lookup tool ends the whole implementation. evidence/probe/lsp/lookup.rb has no handler for the ruby-lsp error.

Observed

Handoff 1257 (change-auditor, implementation 144):

UNKNOWN: Evidence CLI crashed with ruby-lsp InvalidLocationError (RubyLsp::Document::Utf16Scanner#find_char_position, via references from evidence/probe/lsp/lookup.rb:34) whenever token OpenRows was ...
{"type":"implementation_failed","reason":"verdict:error", ... "agent_type":"change-auditor","elapsed_s":400.0,"verdict":"error"}

Reproduce

Run the os-sdlc evidence CLI with a references lookup for token OpenRows on main at or after commit a1baa30b (files plugins/os-sdlc/lib/os_sdlc/crc/open_rows.rb, crc/open_rows/row.rb, tests/crc/progress_table_open_rows_test.rb).

Cause

The ruby-lsp daemon keeps the text of a file from the first time it opens that file. Daemon::LspSession#open_document (lib/os_sdlc/daemon/lsp_session.rb) records the path in @opened_paths and returns early on later calls; it never sends didChange or re-opens. The daemon for root .../crc-os-sdlc-run-4/plugins/os-sdlc opened crc/open_rows.rb at 54 lines (15:44); commit a1baa30b grew it to 60 lines. Evidence::Fingerprint#method_probes (fingerprint.rb:92) takes lines from the current disk file, so the references request for def revisit_files (line 55, column 11) fell past the daemon copy. ruby-lsp 0.26.11 raised InvalidLocationError; Daemon::Server#reply (daemon/server.rb:69) returned it as {"error"=>...}; Daemon::Client#answer (client.rb:50) raised a bare RuntimeError; nothing between Lookup#references_for (lookup.rb:34) and Thor rescues it, so one token aborted the whole evidence run. bin/lsp-probe stop on that root cleared it: bin/evidence --root . OpenRows then exited 0.

Approach

Two fixes. (a) removes the cause; (b) stops any other ruby-lsp error from aborting the run.

(a) Daemon::LspSession#open_document remembers a content digest per opened path. When the file on disk differs from the opened text, it sends the new text to ruby-lsp (textDocument/didChange with full text, or close and re-open) before the request.

(b) Daemon::Client#answer raises Daemon::LspError < StandardError for an error reply. Evidence::Probe::Lsp::Lookup#references_for rescues only Daemon::LspError and returns an unconfirmed result with the error text in lines and the refs command kept. The renderer shows that token as UNCONFIRMED, not PHANTOM, because an empty sender list must not read as "no senders".

Required work: none beyond the acceptance criteria.

Acceptance criteria

  1. Given a daemon session that opened a file, when the file on disk changes and a references request then names a line past the old line count, then the request answers from the new text and raises no error.
  2. Given an error reply from the daemon, when Daemon::Client reads it, then it raises Daemon::LspError.
  3. Given one token whose references lookup raises Daemon::LspError, when bin/evidence collects several tokens, then that token renders UNCONFIRMED with the error text, the other tokens render, and the process exits 0.

Owner sketch

module OsSdlc
  module Daemon
    class LspError < StandardError; end
    class LspSession
      def open_document(path)
    end
  end
  class Evidence
    class Probe
      class Lsp
        class Lookup
          def call
        end
      end
    end
  end
end

Tests: extend tests/evidence/probe/lsp_lookup_location_test.rb (its fake runner raises Daemon::LspError) for AC3; add AC1 beside the LspSession tests.

Origin

  • Trigger: /os-sdlc:implement 1280, implementation 144
  • Improvised this session: re-opened the implementation (146) instead of marking #1280 blocked
  • Chain: implementation_failed ← change-auditor verdict error ← evidence CLI crash ← ruby-lsp InvalidLocationError in Utf16Scanner ← UPSTREAM-TOOL (ruby-lsp; version not checked)
  • Root candidate: none found; searched InvalidLocationError, Utf16Scanner, find_char_position, ruby-lsp, lsp/lookup, Evidence CLI, evidence crash
  • Where: plugins/os-sdlc/lib/os_sdlc/evidence/probe/lsp/lookup.rb
  • Session: c1bedec6-7e01-45ab-b9db-22d63652c19f
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-crc-os-sdlc-run-4/c1bedec6-7e01-45ab-b9db-22d63652c19f.jsonl

VERDICT: CREATE

## Problem os-sdlc implementation 144 (#1280) failed at its first dispatch. The change-auditor (dispatch 1224) ran 400 s, wrote a complete work list, and returned verdict `error` because the evidence CLI crashed. One crash in a lookup tool ends the whole implementation. `evidence/probe/lsp/lookup.rb` has no handler for the ruby-lsp error. ## Observed Handoff 1257 (change-auditor, implementation 144): ``` UNKNOWN: Evidence CLI crashed with ruby-lsp InvalidLocationError (RubyLsp::Document::Utf16Scanner#find_char_position, via references from evidence/probe/lsp/lookup.rb:34) whenever token OpenRows was ... ``` ``` {"type":"implementation_failed","reason":"verdict:error", ... "agent_type":"change-auditor","elapsed_s":400.0,"verdict":"error"} ``` ## Reproduce Run the os-sdlc evidence CLI with a references lookup for token `OpenRows` on main at or after commit a1baa30b (files plugins/os-sdlc/lib/os_sdlc/crc/open_rows.rb, crc/open_rows/row.rb, tests/crc/progress_table_open_rows_test.rb). ## Cause The ruby-lsp daemon keeps the text of a file from the first time it opens that file. `Daemon::LspSession#open_document` (lib/os_sdlc/daemon/lsp_session.rb) records the path in `@opened_paths` and returns early on later calls; it never sends `didChange` or re-opens. The daemon for root `.../crc-os-sdlc-run-4/plugins/os-sdlc` opened `crc/open_rows.rb` at 54 lines (15:44); commit a1baa30b grew it to 60 lines. `Evidence::Fingerprint#method_probes` (fingerprint.rb:92) takes lines from the current disk file, so the references request for `def revisit_files` (line 55, column 11) fell past the daemon copy. ruby-lsp 0.26.11 raised `InvalidLocationError`; `Daemon::Server#reply` (daemon/server.rb:69) returned it as `{"error"=>...}`; `Daemon::Client#answer` (client.rb:50) raised a bare `RuntimeError`; nothing between `Lookup#references_for` (lookup.rb:34) and Thor rescues it, so one token aborted the whole evidence run. `bin/lsp-probe stop` on that root cleared it: `bin/evidence --root . OpenRows` then exited 0. ## Approach Two fixes. (a) removes the cause; (b) stops any other ruby-lsp error from aborting the run. (a) `Daemon::LspSession#open_document` remembers a content digest per opened path. When the file on disk differs from the opened text, it sends the new text to ruby-lsp (`textDocument/didChange` with full text, or close and re-open) before the request. (b) `Daemon::Client#answer` raises `Daemon::LspError < StandardError` for an error reply. `Evidence::Probe::Lsp::Lookup#references_for` rescues only `Daemon::LspError` and returns an unconfirmed result with the error text in `lines` and the refs command kept. The renderer shows that token as UNCONFIRMED, not PHANTOM, because an empty sender list must not read as "no senders". Required work: none beyond the acceptance criteria. ## Acceptance criteria 1. Given a daemon session that opened a file, when the file on disk changes and a references request then names a line past the old line count, then the request answers from the new text and raises no error. 2. Given an error reply from the daemon, when `Daemon::Client` reads it, then it raises `Daemon::LspError`. 3. Given one token whose references lookup raises `Daemon::LspError`, when `bin/evidence` collects several tokens, then that token renders UNCONFIRMED with the error text, the other tokens render, and the process exits 0. ## Owner sketch ```ruby module OsSdlc module Daemon class LspError < StandardError; end class LspSession def open_document(path) end end class Evidence class Probe class Lsp class Lookup def call end end end end end ``` Tests: extend tests/evidence/probe/lsp_lookup_location_test.rb (its fake runner raises `Daemon::LspError`) for AC3; add AC1 beside the LspSession tests. ## Origin - Trigger: /os-sdlc:implement 1280, implementation 144 - Improvised this session: re-opened the implementation (146) instead of marking #1280 blocked - Chain: implementation_failed ← change-auditor verdict error ← evidence CLI crash ← ruby-lsp InvalidLocationError in Utf16Scanner ← UPSTREAM-TOOL (ruby-lsp; version not checked) - Root candidate: none found; searched InvalidLocationError, Utf16Scanner, find_char_position, ruby-lsp, lsp/lookup, Evidence CLI, evidence crash - Where: plugins/os-sdlc/lib/os_sdlc/evidence/probe/lsp/lookup.rb - Session: c1bedec6-7e01-45ab-b9db-22d63652c19f - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os--claude-worktrees-crc-os-sdlc-run-4/c1bedec6-7e01-45ab-b9db-22d63652c19f.jsonl VERDICT: CREATE
Author
Owner

Work started in session c1bedec6, worktree crc-os-sdlc-run-4 (branch crc-os-sdlc-run-4), through /os-sdlc:implement.

Work started in session c1bedec6, worktree crc-os-sdlc-run-4 (branch crc-os-sdlc-run-4), through /os-sdlc:implement.
Author
Owner

Resolution

Done: LspSession keeps each opened file's text (OpenedDocuments) and sends didChange when the text on disk differs, so a long-running daemon no longer queries stale text. Client#answer raises Daemon::LspError on an LSP error reply; Lookup#references_for rescues it and SenderEntries renders the token as UNCONFIRMED instead of crashing. OpenNotification renamed DocumentNotification. Commit cdac7e7f, implementation 149.

Evidence: Pipeline implementation 149 complete: suite-check pass twice (64 s, 52 s), contract-auditor pass. bin/evidence --root plugins/os-sdlc OpenRows exits 0 on real code.

Follow-ups: Runner staging defect seen in this run posted as an occurrence on #925 (to be fixed next). Lens proposals (A2/A15 error placement) go through the lens challenge and are filed as Lens proposal tickets if justified.

## Resolution **Done:** LspSession keeps each opened file's text (OpenedDocuments) and sends didChange when the text on disk differs, so a long-running daemon no longer queries stale text. Client#answer raises Daemon::LspError on an LSP error reply; Lookup#references_for rescues it and SenderEntries renders the token as UNCONFIRMED instead of crashing. OpenNotification renamed DocumentNotification. Commit cdac7e7f, implementation 149. **Evidence:** Pipeline implementation 149 complete: suite-check pass twice (64 s, 52 s), contract-auditor pass. bin/evidence --root plugins/os-sdlc OpenRows exits 0 on real code. **Follow-ups:** Runner staging defect seen in this run posted as an occurrence on #925 (to be fixed next). Lens proposals (A2/A15 error placement) go through the lens challenge and are filed as Lens proposal tickets if justified.
jared closed this issue 2026-10-23 21:20:49 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1284
No description provided.