Repository navigation
bash-language-server hardcodes --external-sources causing unbounded ShellCheck memory growth #16209
Description
Summary
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:
// Additional ShellCheck arguments. Note that we already add the following arguments: --shell, --format, --external-sources.
The --external-sources flag 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
Environment
- OpenCode 1.2.17, macOS, Apple Silicon, 64 GB RAM
- Project: ~460 shell scripts with cross-sourcing via
source-path=SCRIPTDIR - Reproduction: run 2+ concurrent
opencode runsessions on a shell-heavy project
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.
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.
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
Activity
marcusquinn commentedon Mar 5, 2026
Suggested Fix (PR-ready diff)
The fix is minimal — 2 files, ~10 lines:
1. Add
OPENCODE_DISABLE_LSPflag (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)Add early return at the top of the LSP initialization:
Alternative: Fix the root cause in BashLS spawn
If a full
OPENCODE_DISABLE_LSPis too broad, at minimum the BashLS spawn should not hardcode--external-sources. The bash-language-server respectsSHELLCHECK_ARGUMENTSenv var — opencode could pass--norcto prevent.shellcheckrcfrom triggering recursive source expansion:export const BashLS: Info = { // ... async spawn(root) { // ... const proc = spawn(binary, args, { cwd: root, env: { ...process.env, BUN_BE_BUN: "1", + // Prevent .shellcheckrc source-path=SCRIPTDIR from causing + // exponential expansion with the hardcoded --external-sources flag + SHELLCHECK_ARGUMENTS: "--norc", }, })The
--norcapproach is the most targeted fix — it prevents.shellcheckrcdirectives from compounding with the hardcoded--external-sources, while still allowing basic shellcheck linting.Happy to submit a PR if either approach is acceptable.
github-actions commentedon Mar 5, 2026
This issue doesn't fully meet our contributing guidelines.
What needs to be fixed:
Please edit this issue to address the above within 2 hours, or it will be automatically closed.
This issue might be a duplicate of existing issues. Please check:
If you believe this was flagged incorrectly, please let a maintainer know.
marcusquinn commentedon Mar 5, 2026
Update: we've now also removed
source-path=SCRIPTDIRfrom our.shellcheckrcand disabled SC1091 globally (PR marcusquinn/aidevops#2939). This eliminates the root cause on our side — even if--external-sourcesleaks through, there's nosource-pathdirective to trigger recursive expansion.However, the upstream hazard remains for other users: any project with
source-path=SCRIPTDIR(or similar) in their.shellcheckrcwill hit the same exponential memory expansion because bash-language-server hardcodes--external-sources. The suggested fixes in the original issue (OPENCODE_DISABLE_LSP flag + SHELLCHECK_ARGUMENTS --norc passthrough) would protect all users.github-actions commentedon Mar 5, 2026
This issue has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.
Feel free to open a new issue that follows our issue templates.
marcusquinn commentedon Mar 6, 2026
Superseded by #16269 (resubmitted with the required issue template). Fix: #16271.