Skip to content

fix(diagnostics): TSLS push capability, reason-aware fallback, PATH-independent LSP discovery - #3

Merged
rewasa merged 11 commits into
mainfrom
fix/ts-diagnostics-durability
Sep 11, 2026
Merged

rewasa merged 11 commits into
mainfrom
fix/ts-diagnostics-durability

Conversation

@rewasa

@rewasa rewasa commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Advertise textDocument.publishDiagnostics client capability in LSP initialize; TSLS publishes push diagnostics only when the client does, so TS files silently degraded to the AST heuristic (real defect, independently reproduced).
  • Reason-aware fallback_reason (unsupported_language, lsp_server_unavailable, lsp_init_failed, no_diagnostics_support, no_diagnostics_response) replaces the misleading generic "LSP server unavailable" warning.
  • PATH-independent LSP server discovery (shutil.which + well-known dirs) with child-env PATH append; validated cold-start under PATH=/usr/bin:/bin.
  • 10 real-LSP tests (real TSLS/Pyright subprocesses, cold + reused server, no mocks), fixtures module, tests/.fixtures/ gitignore entry.
  • README: durability & revision-pinning section (pin manifest lives in scripts repo, non-destructive restore).

Verification

  • Full suite: pytest tests/ -q → 104 passed (no skips).
  • Real health checker (Hermes venv): HEALTHY, all diagnostics checks green, rc=0.
  • Reduced-PATH E2E: env PATH=/usr/bin:/bin cold subprocess → TS error file method=lsp, count 2.

Notes

  • Foreign __init__.py WIP explicitly excluded (byte-identical, untouched).
  • Durability wiring (pin manifest, restore helper, maintenance gate) lands in the companion scripts-repo PR.

…ATH-independent LSP discovery

- Advertise textDocument.publishDiagnostics client capability in initialize;
  TSLS publishes push diagnostics only when the client does, so TS files
  silently degraded to ast_heuristic before.
- Reason-aware fallback_reason (unsupported_language, lsp_server_unavailable,
  lsp_init_failed, no_diagnostics_support, no_diagnostics_response) replaces
  the misleading generic "LSP server unavailable" warning.
- PATH-independent LSP server discovery (shutil.which + well-known dirs) with
  child-env PATH append so cold starts work under reduced PATH.
- 10 real-LSP tests (cold + reused server, real TSLS/Pyright subprocesses),
  fixtures module, tests/.fixtures gitignore entry.
- README: TSLS push-capability + fallback-reason + durability/revision-pinning
  docs.
CI ran without any LSP server, so the three live-LSP tests inside
TestFallbackReasons (reduced-path discovery, silent pull-capable server,
empty-vs-none contract) asserted real server behavior that cannot exist
without the binaries and failed on every runner. Now:

- CI installs node 20 + npm -g typescript typescript-language-server
  pyright, so live diagnostics tests actually run on CI instead of being
  skipped everywhere (no blanket skip: the live coverage stays mandatory
  and now executes on all four Python versions).
- The three live tests carry accurate prerequisite skip guards as a
  fallback for environments without LSP servers, matching the pattern the
  live test classes already use.
The previous run hung >50min in "Run tests" on all 4 matrix jobs with no
output (live-LSP tests against cold tsserver/pyright). pytest-timeout now
bounds every test at 120s (thread method), faulthandler dumps stacks, and a
final always-step prints the junit tail so the stuck test and its stack are
visible in the job log.
ensure_initialized() holds _init_lock while calling _start_and_init(); when
the cold initialize timed out on CI, _start_and_init() called shutdown()
which tried to acquire the SAME non-reentrant threading.Lock -> permanent
self-deadlock (whole CI suite stalled >50min, no output). Switch to RLock.
Also raise _LSP_INIT_TIMEOUT 15s->30s: CI cold-starts tsserver from npm
caches; the 15s cap made the first initialize spuriously time out, which is
exactly what triggered the deadlock path.
TSLS 5.3.0 accepted the initialize request but never replied (30s timeout,
every TS diagnostics test degraded to ast_heuristic). Local 5.1.3 answers
instantly. Pin the known-good version and log the global npm tree.
….8.3

CI npm -g resolved typescript@7.0.2 (npm latest). TSLS spawns tsserver during
initialize; TS 7 lib layout broke that -> initialize never answered (30s
timeout, every TS test degraded to ast_heuristic). Local proof: TSLS logs
"Using Typescript version 5.8.3" BEFORE answering. Pin typescript@5.8.3 +
TSLS 5.1.3. Keep scripts/tsls_probe.js handshake probe for future diagnosis.
…che glob)

test_reduced_path ran a cold subprocess with PATH=/usr/bin:/bin and relied
on _LSP_EXTRA_BIN_DIRS fallbacks; CI installs LSP servers via npm -g into
/opt/hostedtoolcache/node/<ver>/{x64/,}bin which was not in the list, so the
server was unresolvable and the test fell back to ast_heuristic. Add the
globbed hostedtoolcache node bin dirs (missing dirs skipped silently).
@rewasa
rewasa merged commit 9fe9516 into main Sep 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant