Repository navigation
bash-language-server hardcodes --external-sources causing unbounded ShellCheck memory growth #16269
Description
Description
The bash-language-server bundled with OpenCode hardcodes --external-sources in every ShellCheck invocation. On projects with many shell scripts that source each other (e.g. 100+ scripts with source-path=SCRIPTDIR in .shellcheckrc), this causes exponential AST expansion — individual shellcheck processes grow to 3-5 GB RSS and never terminate.
With multiple concurrent headless sessions (opencode run), each spawning its own bash-language-server, this compounds rapidly. Today we observed 73 concurrent shellcheck processes consuming 17.8 GB RAM on a 64 GB machine.
Root Cause: In bash-language-server/out/config.js line 24, --external-sources is hardcoded and cannot be disabled. When combined with a project's .shellcheckrc containing source-path=SCRIPTDIR, ShellCheck follows all source directives recursively across every script in the project, causing exponential memory growth.
The bash-language-server does support SHELLCHECK_PATH and SHELLCHECK_ARGUMENTS env vars for configuration, but:
- There's no way to remove the hardcoded
--external-sourcesflag - There's no
OPENCODE_DISABLE_LSPenv var to disable LSP entirely (onlyOPENCODE_DISABLE_LSP_DOWNLOADwhich doesn't affect already-installed LSPs) - Headless
opencode runsessions don't need LSP diagnostics at all, but there's no way to disable them
Plugins
None relevant — this is core bash-language-server behavior.
OpenCode version
1.2.17
Steps to reproduce
- Create a project with 50+ shell scripts that cross-source each other
- Add
source-path=SCRIPTDIRto.shellcheckrc - Run 2+ concurrent
opencode runsessions on the project - Observe shellcheck processes growing to multi-GB RSS and never terminating
- Single session also reproduces, just slower to hit critical memory
Operating System
macOS, Apple Silicon, 64 GB RAM
Terminal
Ghostty / headless (opencode run)
Suggested Fixes (any of these would help)
1. Add OPENCODE_DISABLE_LSP=true env var (simplest, highest impact)
Disable all LSP servers when set. Headless opencode run sessions don't benefit from LSP diagnostics — the LLM doesn't use them. This would eliminate the entire class of LSP memory issues for automated/CI use cases.
2. Don't hardcode --external-sources in bash-language-server invocation
Let users control this via SHELLCHECK_ARGUMENTS or a config option. The --external-sources flag is the direct cause of the exponential expansion. Without it, ShellCheck completes in milliseconds.
3. Add per-process memory/time limits to LSP child processes
ShellCheck processes spawned by the language server should have a timeout (e.g. 30s) and memory limit. A single shellcheck invocation should never run for 35+ minutes or consume 5+ GB.
4. Share LSP instances across sessions (related: #13041)
Multiple opencode run sessions on the same project each spawn their own bash-language-server + shellcheck processes. A shared LSP daemon would reduce the multiplier.
PR-ready diff
The fix is minimal — 2 files, ~10 lines:
1. Add OPENCODE_DISABLE_LSP flag (packages/opencode/src/flag/flag.ts)
export const OPENCODE_DISABLE_LSP_DOWNLOAD = truthy("OPENCODE_DISABLE_LSP_DOWNLOAD")
+ export const OPENCODE_DISABLE_LSP = truthy("OPENCODE_DISABLE_LSP")2. Check flag before spawning LSP servers (packages/opencode/src/lsp/client.ts)
+ if (Flag.OPENCODE_DISABLE_LSP) {
+ log.info("LSP disabled via OPENCODE_DISABLE_LSP")
+ return
+ }Alternative: Fix the root cause in BashLS spawn
If a full OPENCODE_DISABLE_LSP is too broad, at minimum the BashLS spawn should not hardcode --external-sources:
export const BashLS: Info = {
// ...
async spawn(root) {
// ...
const proc = spawn(binary, args, {
cwd: root,
env: {
...process.env,
BUN_BE_BUN: "1",
+ SHELLCHECK_ARGUMENTS: "--norc",
},
})Current Workaround
We use a shellcheck-wrapper.sh that strips --external-sources from arguments before passing to the real shellcheck binary, configured via SHELLCHECK_PATH env var. This works but requires manual setup and the env var must be set in .zshenv (not .zshrc) to affect non-interactive shells.
We've also removed source-path=SCRIPTDIR from our .shellcheckrc and disabled SC1091 globally, which eliminates the root cause on our side. However, the upstream hazard remains for any project using source-path=SCRIPTDIR.
Related Issues
- opencode eating 70gb of memory? #5363 — opencode eating 70gb of memory (36 comments, same root cause pattern)
- Per-session MCP server and LSP duplication multiplies memory usage across concurrent sessions #13041 — Per-session MCP/LSP duplication multiplies memory
- Memory leak, LSP spam, 1 Frame per minute #13796 — Memory leak, LSP spam
- Disabling LSP for specific file extensions #4957 — Disabling LSP for specific file extensions
- [FEATURE]: Deactivate unused LSP servers during a session #12976 — Deactivate unused LSP servers
- [FEATURE]: TUI whole and LSP memory limits as part of configuration #8947 — TUI/LSP memory limits config
Resubmitted from #16209 which was auto-closed for not using the bug report template.
Activity
feat(lsp): add OPENCODE_DISABLE_LSP env var to skip all LSP servers
marcusquinn commentedon Mar 6, 2026
Updated analysis — root cause is upstream in bash-language-server
After investigating further, the
--external-sourcesflag is hardcoded in bash-language-server (server/src/shellcheck/index.ts:142), not in opencode. The existingshellcheckArguments/SHELLCHECK_ARGUMENTSconfig can only append arguments — it cannot remove the hardcoded flag. This means the root cause cannot be fixed from the opencode side alone.Two complementary fixes submitted
1. opencode —
OPENCODE_DISABLE_LSP=trueenv var (this repo)opencode runsessions that don't benefit from diagnostics2. bash-language-server —
shellcheckExternalSourcesconfig (upstream)--external-sourcesoptional (defaulttruefor backward compat)Revised suggested fixes for this issue
OPENCODE_DISABLE_LSP=trueenv var--external-sourcesconfigurableFix 1 is the immediate opencode-side mitigation. Fix 2 addresses the root cause upstream. Fixes 3 and 4 are architectural improvements that would help with other LSP memory issues beyond bash-language-server.
marcusquinn commentedon Mar 6, 2026
Upstream fix merged
The root cause fix has been merged upstream: bash-lsp/bash-language-server#1376.
bash-language-server now supports a
shellcheckExternalSourcesboolean config (defaulttruefor backward compat). Setting it tofalse— via VS Code setting, LSP init param, orSHELLCHECK_EXTERNAL_SOURCES=falseenv var — omits the--external-sourcesflag entirely, preventing the unbounded memory growth.Updated status
OPENCODE_DISABLE_LSP=trueenv var--external-sourcesconfigurableWhat this means for opencode
Once opencode bumps its bash-language-server dependency to a version that includes this change, users can set
SHELLCHECK_EXTERNAL_SOURCES=falseto prevent the memory explosion without disabling LSP entirely. PR #16271 (OPENCODE_DISABLE_LSP) remains valuable for headless sessions that don't need LSP at all.ksp-apoc commentedon Mar 21, 2026
+1 — Reproduced on OpenCode v1.2.27 (Linux, 30GB RAM system).
Working directory:
~(home directory, not a specific project)bash-language-server process: Grew to 22.5 GB RSS + 5.6 GB swap within ~8 minutes of starting OpenCode. CPU usage was 563% (26 threads), OOM score hit 984/1000.
The system had only 293 MB free memory remaining (out of 30 GB) before I manually killed the process.
Root cause confirmation: OpenCode's LSP auto-launched bash-language-server against
$HOME, which recursively scannednode_modules/,.cache/,.local/, etc. — triggering the--external-sourcesexponential expansion described in this issue.Workaround applied:
kill <pid>+ addingexternal-sources=falseto~/.shellcheckrc. Looking forward to theOPENCODE_DISABLE_LSPenv var from PR #16271.marcusquinn commentedon Mar 22, 2026
Thanks for confirming the reproduction @ksp-apoc — 22.5 GB RSS is consistent with what we saw. The upstream fix in bash-language-server has been merged (bash-lsp/bash-language-server#1376), so this should resolve once OpenCode bumps their bash-language-server dependency.
In the meantime, setting
SHELLCHECK_EXTERNAL_SOURCES=falseas an environment variable before starting OpenCode is the most reliable workaround. This disables the--external-sourcesflag that causes the exponential AST expansion.The OpenCode team can track the upstream release and bump when ready — nothing more needed from our side on this issue.
thdxr commentedon Apr 2, 2026
Closing in favor of #20695 where we're collecting all memory-related problems. Please follow that issue for updates.