Description
Summary
The bash-language-server spawns shellcheck with --external-sources and --source-path flags when linting via stdin. When the source path includes directories with scripts that transitively source many files (or circular source chains), shellcheck's memory usage grows unboundedly — observed at 19.8 GB RSS (39.4% of 48 GB system RAM) and 97% CPU before manual kill.
Observed Behaviour
The language server (running as an MCP tool under opencode) spawned:
shellcheck --shell=bash --format=json1 --external-sources \
--source-path=file:///Users/justin/Git/ilds \
--source-path=/Users/justin/.aidevops/agents/scripts \
-
Process state before kill:
| PID | %CPU | %MEM | RSS |
|---|---|---|---|
| 31555 | 97.2 | 39.4 | 19,854,400 KB (~19.8 GB) |
The process ran for approximately 4 minutes before manual intervention. The system (48 GB M3 Max MacBook Pro) was approaching an OOM crash.
Root Cause Analysis
The --external-sources flag instructs shellcheck to follow source/. directives in scripts. Combined with --source-path pointing at directories containing dozens of shell scripts that transitively source each other (a DevOps framework with ~50 helper scripts using source "${SCRIPT_DIR}/shared-constants.sh" patterns), shellcheck appears to:
- Recursively resolve and parse all transitively sourced files
- Accumulate parsed ASTs in memory without bounds
- Possibly hit circular source chains (A sources B, B sources A) without cycle detection, or at minimum without a memory cap
The language server does not appear to impose any timeout or memory limit on the shellcheck subprocess.
Expected Behaviour
- shellcheck subprocess should have a memory limit or timeout to prevent runaway resource consumption
- Circular source chains should be detected and short-circuited
- At minimum, a configurable
--external-sourcestoggle in the language server settings so users can disable it for projects with complex source trees
Environment
- bash-language-server: installed via npm (opencode bundled)
- shellcheck: 0.10.0 (Homebrew)
- OS: macOS 15 (Apple Silicon M3 Max, 48 GB RAM)
- Editor/Client: opencode 1.2.16 (MCP-based LSP host)
Suggested Fix
- Wrap the shellcheck invocation with a timeout (e.g., 30 seconds) and memory limit
- Consider making
--external-sourcesopt-in rather than default, or at least expose it as a configuration option (shellcheck.externalSources: boolean) - If
--external-sourcesis enabled, consider limiting--source-pathdepth or file count
Workaround
Adding external-sources=false to .shellcheckrc in the project root prevents the runaway, but also disables legitimate external source checking.
Activity
skovhus commentedon Mar 5, 2026
Thanks for reporting. Contributions are more than welcome to fix this.
feat: add shellcheckExternalSources config to make --external-sources…
marcusquinn commentedon Mar 6, 2026
We hit the exact same issue — 73 concurrent shellcheck processes consuming 17.8 GB on a 64 GB machine, triggered by opencode headless sessions on a project with 100+ cross-sourcing shell scripts.
Submitted a PR to fix the root cause: #1376
Adds a
shellcheckExternalSourcesboolean config (defaulttrue) so users can disable--external-sourceswithout needing a wrapper script. The existingshellcheckArgumentsconfig can only append args — it can't remove the hardcoded--external-sources.We've also submitted a complementary fix on the opencode side (anomalyco/opencode#16271) adding
OPENCODE_DISABLE_LSP=trueto skip all LSP servers entirely for headless sessions.