Repository navigation
fix(lsp): a tool call uses the session's server - #116
Merged
Merged
Conversation
lsp/tools.go started a language server per call and never told the package,
so IsAvailable() stayed false after a successful call: /diagnostics reported
"LSP not available (set lsp.enabled=true in config.json)" — advice that is
wrong when LSP is on — and every call paid another start.
That per-call server was also initialized with the *file* as its workspace
root, which gopls answers with "LSP error 0: no views". Because main.go
registers the four LSP tools unconditionally and only calls lsp.Init when
lsp.enabled is set, the shipped default reached exactly that path: all four
tools failed outright, they did not merely leave a stale status line.
- lsp/touch.go: Ensure(filePath) starts the session's server through the
lazyStart SyncFile already uses, under the same lock contract.
- lsp/tools.go: with a workspace configured, promote that server and use it;
without one, keep the per-call server, now rooted at the project directory.
Measured with LSP_TEST=1 and gopls 0.23.0, cold session, no file read:
before: 0 bytes, err "LSP error 0: no views", IsAvailable=false
after: 223 bytes of symbols, IsAvailable=true, and one "started for"
line across two tool calls.
The two gated tests in lsp/tools_test.go pin both routes; the scenario
lsp-tools.scenario pins the screen a user sees.
The scenario runs the binary with HOME=/tmp/tinyscen-home-lsp, and gopls resolves the workspace through that HOME: it fills $HOME/go/pkg/mod, whose directories are read-only. `rm -rf` needs write permission on them, so the wipe exited 1, the `&&` chain stopped before the stub was started, and the second run of the scenario died on "connection refused" — an error about a server nobody launched, not about LSP. Measured after one run: 96M under the throwaway HOME, `rm -rf` exit 1, `chmod -R u+w` then `rm -rf` exit 0. Drop the write permission first, and keep the wipe outside the && chain so a wipe that still fails cannot stop the stub from starting. Refs #115
lsp-diagnostics starts the server with a file read, which is the route #111 fixed. This one reaches the same status line from the other side: the stub asks for lsp_symbols (the name lsp.ToolFactory registers) and nothing reads or writes a file, so "No LSP diagnostics." is only reachable if the tool call promoted the session's server — the regression guard for #114. make test-tui-lsp now runs every lsp-*.scenario instead of naming one file, and the scenario suite skips them by that pattern: a second LSP scenario that only the file name knew about would have been skipped in silence. Refs #114
docs/tui-verification.md gains the lsp-tools paragraph (what makes its assertion about the tool route, and why the stub must name the registered tool), and both documents gain the wipe rule: an lsp-* scenario drops the write permission before removing its throwaway HOME, because gopls leaves a read-only module cache under it (issue #115). Refs #114, #115
make test-tui-lsp runs every lsp-*.scenario: lsp-diagnostics starts the server with a file read, lsp-tools with an LSP tool call. Name the step and its comment for what it runs. Refs #114
Returning Ensure's error ended the call where the per-call server could still have answered: a workspace is configured for the session as a whole, so a file belonging to another project — or one whose language has its own server — lost the route that served it before. Promote the session's server when that works and let the per-call server below be the second attempt, which also keeps this change additive to the behaviour it replaces. Refs #114
This was referenced Oct 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
lsp/touch.go: newEnsure(filePath)— starts the session's language server through the samelazyStartSyncFilealready uses, under the same lock contract.lsp/tools.go: with a workspace configured, a tool call promotes that server and uses it; theper-call server below stays the second attempt, now rooted at the project directory. The
change is additive:
maybe promote → if IsAvailable() → persistent, else → per-call.lsp/tools_test.go: two gated tests (LSP_TEST=1) pin both routes.tui/testdata/scenarios/lsp-tools.scenario, plus anlspmode in the stub and amake test-tui-lspthat runs everylsp-*.scenario(the suite skips them by that pattern):the screen-level guard for [lsp] Tool calls start a server per call and never tell /diagnostics about it #114.
lsp-*scenarios wipe their throwawayHOMEwithchmod -R u+w …; rm -rf …, outside the&&chain that starts the stub — fixes [tui] The LSP scenarios fail on their second run: a read-only module cache in the throwaway HOME #115.Why
The per-call server was the only route a session without a configured workspace had, and it was
broken: gopls answers
LSP error 0: no viewswheninitializenames a file as the workspace.Because
main.goregisters the four LSP tools unconditionally and only callslsp.Initwhenlsp.enabledis set, the shipped default failed every LSP tool call.Two alternatives were rejected. Fixing only the rooting would have left
/diagnosticsreporting"LSP not available (set lsp.enabled=true in config.json)" — wrong advice — and paid a server start
per call. Returning
Ensure's error instead of falling through would have ended the call where theper-call server could still answer: a workspace is configured for the session as a whole, so a file
belonging to another project, or one whose language has its own server, would lose the route that
served it before.
Verification
LSP_TEST=1 go test ./lsp/ -count=1→ ok, both new tests pass (gopls 0.23.0 onPATH).if Initialised()→if falsemakesTestToolCallPromotesTheSessionServerfail with "a tool call left the session without a client"and "server started 0 times across two calls". Re-checked against the final shape.
Initialize("file://"+canonicalPath(rootDir))→Initialize(fileURI)makes
TestToolCallWithoutWorkspaceServesTheRequestfail withLSP error 0: no views.lsp/tools.go+lsp/touch.gofrom before this branch and thebinary rebuilt,
lsp-tools.scenarioseeslsp_symbolsand then times out waiting forNo LSP diagnostics.(exit 1).0 bytes,err=LSP error 0: no views,IsAvailable=false. After:223 bytesof symbols,IsAvailable=true, and oneLSP: gopls started for …across two calls.make test-tui-lsptwice in a row → exit 0 both times,ok: 14 stepsper scenario, on thefinal revision. Before this branch the second run died on
connection refused([tui] The LSP scenarios fail on their second run: a read-only module cache in the throwaway HOME #115).go test ./... -count=1→ 13 packages ok.go vet ./...clean.make test-tui-scenarios→26 scenarios ok, 3 skipped (
live-answer,lsp-diagnostics,lsp-tools).Honest scope
DefaultConfigswere notexercised locally; CI's
lspjob runs the gated tests for gopls and typescript.case "persistent client asked about another project's file" was not measured, and nothing is
claimed about it.
/diagnostics's listing outcome still needs real diagnostics present, and stays uncovered.Follow-ups
LSP client not available" it was filed on wasunknown tool: document_symbols(30 characters),from a stub that named a tool no code registers.