feat: add gopls LSP plugin support for Claude code running in the sandbox#815
Conversation
Install gopls v0.18.1 in the code agent container image and add a generic Plugins field to the harness schema. The runner bootstraps plugins as Claude Code marketplace-cached plugins so the LSP tool registers for semantic Go code intelligence (go-to-definition, find-references, call hierarchy) inside OpenShell sandboxes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
fullsend review is working on this — view logs |
Site previewPreview: https://62154cf1-site.fullsend-ai.workers.dev Commit: |
Review: #815Head SHA: 1ffc74a SummaryThis PR adds gopls (Go Language Server) support to the code agent sandbox, enabling Claude Code's LSP tool for semantic Go code intelligence. The implementation is well-structured: it adds a generic FindingsMedium
Low
Info
FooterOutcome: approve Previous runReview: #815Head SHA: fdcf618 SummaryThe PR adds gopls LSP plugin support to the sandbox — a well-structured feature with good security hygiene (input validation, injection scanning, path traversal protection, JSON re-serialization). The marketplace emulation approach is clearly motivated by an empirical limitation in Claude Code's FindingsHigh
Medium
Low
Info
FooterOutcome: request-changes Previous run (2)Review: #815Head SHA: ba52a68 SummaryThis PR adds gopls language server support to the code agent sandbox by installing the binary in the container image, adding a FindingsMedium
Low
Info
FooterOutcome: comment-only |
|
Hello! Could you send us the workflows in which this was tested? I'm interested in the transcripts to make sure it is recognizing the feature and using it properly. |
gopls LSP Plugin — End-to-End ValidationExperiment configuration filesexperiments/gopls-lsp-validation/.fullsend/harness/lsp-callgraph.yamlexperiments/gopls-lsp-validation/.fullsend/agents/lsp-callgraph.mdexperiments/gopls-lsp-validation/.fullsend/plugins/gopls-lsp/plugin.jsonexperiments/gopls-lsp-validation/.fullsend/plugins/gopls-lsp/.lsp.jsonexperiments/gopls-lsp-validation/.fullsend/policies/lsp-callgraph.yamlexperiments/gopls-lsp-validation/.fullsend/env/gcp-vertex.envexperiments/gopls-lsp-validation/.fullsend/target-repo/main.goexperiments/gopls-lsp-validation/.fullsend/target-repo/go.modFullsend Run$ /bin/fullsend run lsp-callgraph --fullsend-dir experiments/gopls-lsp-validation/.fullsend --target-repo experiments/gopls-lsp-validation/.fullsend/target-repo --output-dir /tmp/fullsend-lsp-test --no-post-script $ RUN=/tmp/fullsend-lsp-test/agent-lsp-callgraph-42943-1778595166 Debug log — gopls server lifecycle$ grep -E LSP MANAGER|LSP server|Loaded.*LSP|LSP PROTOCOL|LSP client /tmp/fullsend-lsp-test/agent-lsp-callgraph-42943-1778595166/iteration-1/claude-debug.log Transcript — LSP tool call count$ grep -c '"name":"LSP"' /tmp/fullsend-lsp-test/agent-lsp-callgraph-42943-1778595166/iteration-1/transcripts/agent.jsonl Transcript — LSP operations breakdown$ grep -o '"operation":"[^"]*"' /tmp/fullsend-lsp-test/agent-lsp-callgraph-42943-1778595166/iteration-1/transcripts/agent.jsonl | sort | uniq -c Transcript — LSP results$ grep -oE '(Document symbols:|Found [0-9]+ (outgoing|incoming) call|Defined in|Call hierarchy item:)[^"]*' /tmp/fullsend-lsp-test/agent-lsp-callgraph-42943-1778595166/iteration-1/transcripts/agent.jsonl Agent output — lsp-analysis.md$ cat /tmp/fullsend-lsp-test/agent-lsp-callgraph-42943-1778595166/iteration-1/output/lsp-analysis.md main() [Line 5] |
rh-hemartin
left a comment
There was a problem hiding this comment.
A couple of things:
- This needs some tests.
- We need to scan plugins as we do with skills (raised by the review agent), even ifthey are JSON.
- It would be nice to instruct the agent to install gopls using a skill within the plugin or something like that. We can explore this path in the near future.
Otherwise LGTM.
|
fullsend review is working on this — view logs |
Tests: Added 17 unit tests covering buildPluginConfigs (single/multiple/no-LSP/invalid-JSON/empty-JSON/structure/empty-list), buildClaudeCommand plugin flags (multiple dirs, quote escaping, nil), harness validation (valid/invalid names, path resolution, traversal rejection, missing plugin), and ShouldScan for plugin files. Extracted buildPluginConfigs helper from bootstrapPlugins to make the JSON-building logic testable without a sandbox.
Plugin scanning: Both plugin.json and .lsp.json are now scanned via scanPipeline before upload, matching the skills scanning pattern. Also added both to the ScannableFiles map for repo-wide context scanning.
Agreed, worth exploring.
|
Security scanning for plugins:
- Scan both plugin.json and .lsp.json via scanPipeline before uploading
to the sandbox, matching the existing skills scanning pattern.
- Add plugin.json and .lsp.json to the ScannableFiles map so they are
included in repo-wide context scanning.
Error handling:
- Check the error return from tmp.Write(data) in the config file upload
loop. Previously ignored, a failed write would silently upload a
corrupt or empty config to the sandbox. Now follows the same
error-check-close-remove pattern used by bootstrapSecurityHooks.
Conditional PATH:
- Gate the /usr/local/go/bin PATH addition on len(h.Plugins) > 0.
Previously unconditional for all agents, it now only applies when
plugins are configured since only gopls requires it.
Testability and test coverage:
- Extract buildPluginConfigs helper from bootstrapPlugins, separating
pure JSON-building logic from sandbox I/O for unit testability.
- Add 17 unit tests across 3 packages:
- buildPluginConfigs: single plugin, multiple plugins, no .lsp.json,
invalid .lsp.json, empty .lsp.json, config structure, empty list
- buildClaudeCommand: multiple plugin dirs, quote escaping, nil plugins
- Harness: valid/invalid plugin names, path resolution, traversal
rejection, missing plugin directory
- ShouldScan: plugin.json, Plugin.json, .lsp.json
Code quality:
- Move scanPipeline nil-check outside the plugin loop to avoid redundant
evaluation and reduce nesting depth.
- Pass mktBase to buildPluginConfigs instead of recomputing it, avoiding
a potential silent divergence between directory creation and config
file paths.
- Fix non-critical findings warning message to use "in %s" preposition,
consistent with the critical findings branch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fdcf618 to
1ffc74a
Compare
|
fullsend review is working on this — view logs |
Summary
Adds gopls (Go Language Server) support to the code agent sandbox, enabling Claude Code's LSP tool for semantic Go code intelligence inside OpenShell sandboxes.
Related to #678 and #718
Problem
The code agent relies entirely on grep/glob/Read for code navigation. Without a language server, it misses type-aware resolution — interface implementations, cross-package references, embedded struct fields, and call hierarchy. Claude Code's LSP tool exists but requires two things the sandbox lacked:
lspServersconfig comes from a marketplace plugin definition, not from inline--plugin-dirpluginsChanges
Container image (
images/code/Containerfile, +6 lines)go installat/usr/local/go/bin/goplsHarness schema (
internal/harness/harness.go, +20 lines)Plugins []stringfield (mirrorsSkillspattern)validPluginNameregex (^[a-zA-Z0-9_-]+$)Runner (
internal/cli/run.go, +140 lines)bootstrapPluginsfunction creates the Claude Code marketplace plugin file structure inside the sandbox:marketplace.json(with requiredownerfield andlspServersconfig read from.lsp.json)known_marketplaces.json,installed_plugins.json,settings.jsonwithenabledPluginssandbox.ExeccallbuildClaudeCommandpasses--plugin-dirflags for defense-in-depth/usr/local/go/binto sandbox PATH (Docker ENV is overridden by sandbox .bashrc)Scaffold (
internal/scaffold/fullsend-repo/, +5 lines)plugins/gopls-lsp/plugin.json+.lsp.json— gopls LSP server configharness/code.yaml— addsplugins: [plugins/gopls-lsp]Tests (
internal/cli/run_test.go, +9 lines)buildClaudeCommandtests for new signatureTestBuildClaudeCommand_WithPluginDirsWhy marketplace emulation?
Claude Code's LSP tool is only registered when
lspServersconfig comes from a marketplace plugin definition (marketplace.json), not from inline--plugin-dirplugins. We verified this empirically:--plugin-dir→ plugin loads, LSP tool NOT registered (23 tools)In
--printmode (which fullsend uses), Claude Code skips marketplace sync, so we pre-populate the marketplace file structure during sandbox bootstrap. The structure matches anthropics/claude-plugins-official.Test results
End-to-end fullsend workflow inside OpenShell sandbox with a Go test project containing a multi-level call chain (
main → orchestrate → buildGreeting → getPrefix → formatOutput):documentSymboloutgoingCallsincomingCallsgoToDefinitionprepareCallHierarchyhoverSecurity
^[a-zA-Z0-9_-]+$before shell interpolation (matches existingvalidAgentNamepattern)ResolveRelativeTowith directory containment check.lsp.jsoncontent parsed viajson.Unmarshaland re-serialized viajson.Marshal(no raw interpolation)/security-review: no findings above confidence thresholdTest plan
go test ./internal/cli/ ./internal/harness/— all tests passgo vet ./...— cleangopls versionsucceeds)documentSymbol,outgoingCalls,incomingCalls,goToDefinitionall return correct results**/goplsin network policy (no network needed for local projects)