Skip to content

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

Closed as not planned

Description

@marcusquinn

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:

  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

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 run sessions 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

Activity

  1. marcusquinn commented on Mar 5, 2026

    @marcusquinn
    Author

    Suggested Fix (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)

    Add early return at the top of the LSP initialization:

    + 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. The bash-language-server respects SHELLCHECK_ARGUMENTS env var — opencode could pass --norc to prevent .shellcheckrc from 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 --norc approach is the most targeted fix — it prevents .shellcheckrc directives from compounding with the hardcoded --external-sources, while still allowing basic shellcheck linting.

    Happy to submit a PR if either approach is acceptable.

  2. github-actions commented on Mar 5, 2026

    @github-actions
    Contributor

    This issue doesn't fully meet our contributing guidelines.

    What needs to be fixed:

    • No issue template was used. Please resubmit using one of the three required templates: Bug Report, Feature Request, or Question. Since this appears to be a bug report, the Bug Report template should be used (it requires a Description field with reproduction context).

    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.

  3. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    perfIndicates a performance issue or need for optimization
    on Mar 5, 2026
  4. marcusquinn commented on Mar 5, 2026

    @marcusquinn
    Author

    Update: we've now also removed source-path=SCRIPTDIR from our .shellcheckrc and disabled SC1091 globally (PR marcusquinn/aidevops#2939). This eliminates the root cause on our side — even if --external-sources leaks through, there's no source-path directive to trigger recursive expansion.

    However, the upstream hazard remains for other users: any project with source-path=SCRIPTDIR (or similar) in their .shellcheckrc will 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.

  5. github-actions commented on Mar 5, 2026

    @github-actions
    Contributor

    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.

  6. marcusquinn commented on Mar 6, 2026

    @marcusquinn
    Author

    Superseded by #16269 (resubmitted with the required issue template). Fix: #16271.

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