Skip to content

ShellCheck subprocess with --external-sources leaks unbounded memory (19.8 GB observed) #1375

Closed

Description

@johnwaldo

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:

  1. Recursively resolve and parse all transitively sourced files
  2. Accumulate parsed ASTs in memory without bounds
  3. 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-sources toggle 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

  1. Wrap the shellcheck invocation with a timeout (e.g., 30 seconds) and memory limit
  2. Consider making --external-sources opt-in rather than default, or at least expose it as a configuration option (shellcheck.externalSources: boolean)
  3. If --external-sources is enabled, consider limiting --source-path depth 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

  1. skovhus commented on Mar 5, 2026

    @skovhus
    Collaborator

    Thanks for reporting. Contributions are more than welcome to fix this.

  2. added a commit that references this issue on Mar 6, 2026
    e5a4ec9
  3. marcusquinn commented on Mar 6, 2026

    @marcusquinn
    Contributor

    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 shellcheckExternalSources boolean config (default true) so users can disable --external-sources without needing a wrapper script. The existing shellcheckArguments config 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=true to skip all LSP servers entirely for headless sessions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions