Skip to content

bash-language-server hardcodes --external-sources causing unbounded ShellCheck memory growth #16269

Closed

Description

@marcusquinn

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:

  1. There's no way to remove the hardcoded --external-sources flag
  2. There's no OPENCODE_DISABLE_LSP env var to disable LSP entirely (only OPENCODE_DISABLE_LSP_DOWNLOAD which doesn't affect already-installed LSPs)
  3. Headless opencode run sessions 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

  1. Create a project with 50+ shell scripts that cross-source each other
  2. Add source-path=SCRIPTDIR to .shellcheckrc
  3. Run 2+ concurrent opencode run sessions on the project
  4. Observe shellcheck processes growing to multi-GB RSS and never terminating
  5. 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

Resubmitted from #16209 which was auto-closed for not using the bug report template.

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    perfIndicates a performance issue or need for optimization
    on Mar 6, 2026
  2. added a commit that references this issue on Mar 6, 2026
    08149de
  3. marcusquinn commented on Mar 6, 2026

    @marcusquinn
    Author

    Updated analysis — root cause is upstream in bash-language-server

    After investigating further, the --external-sources flag is hardcoded in bash-language-server (server/src/shellcheck/index.ts:142), not in opencode. The existing shellcheckArguments / SHELLCHECK_ARGUMENTS config 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=true env var (this repo)

    2. bash-language-server — shellcheckExternalSources config (upstream)

    • PR: bash-lsp/bash-language-server#1376
    • Adds a boolean config option to make --external-sources optional (default true for backward compat)
    • Fixes the root cause for all editors/tools, not just opencode
    • References existing upstream issues: #874 (open since 2023) and #1375

    Revised suggested fixes for this issue

    # Fix Where Status
    1 OPENCODE_DISABLE_LSP=true env var opencode PR #16271
    2 Make --external-sources configurable bash-language-server PR #1376
    3 Per-process memory/time limits on LSP children opencode Not yet submitted — separate scope
    4 Share LSP instances across sessions opencode Not yet submitted — relates to #13041

    Fix 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.

  4. marcusquinn commented on Mar 6, 2026

    @marcusquinn
    Author

    Upstream fix merged

    The root cause fix has been merged upstream: bash-lsp/bash-language-server#1376.

    bash-language-server now supports a shellcheckExternalSources boolean config (default true for backward compat). Setting it to false — via VS Code setting, LSP init param, or SHELLCHECK_EXTERNAL_SOURCES=false env var — omits the --external-sources flag entirely, preventing the unbounded memory growth.

    Updated status

    # Fix Where Status
    1 OPENCODE_DISABLE_LSP=true env var opencode PR #16271 (open)
    2 Make --external-sources configurable bash-language-server Merged — #1376
    3 Per-process memory/time limits on LSP children opencode Not yet submitted
    4 Share LSP instances across sessions opencode Not yet submitted — relates to #13041

    What 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=false to 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.

  5. ksp-apoc commented on Mar 21, 2026

    @ksp-apoc

    +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.

    PID 402991 — bash-language-server
    VmRSS:  23,481,740 kB (22.4 GB)
    VmSwap:  5,781,456 kB (5.5 GB)
    VmSize: 105,270,736 kB (100 GB virtual)
    Threads: 26
    

    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 scanned node_modules/, .cache/, .local/, etc. — triggering the --external-sources exponential expansion described in this issue.

    Workaround applied: kill <pid> + adding external-sources=false to ~/.shellcheckrc. Looking forward to the OPENCODE_DISABLE_LSP env var from PR #16271.

  6. marcusquinn commented on Mar 22, 2026

    @marcusquinn
    Author

    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=false as an environment variable before starting OpenCode is the most reliable workaround. This disables the --external-sources flag 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.

  7. thdxr commented on Apr 2, 2026

    @thdxr
    Member

    Closing in favor of #20695 where we're collecting all memory-related problems. Please follow that issue for updates.

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

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)perfIndicates a performance issue or need for optimization

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions