Skip to content

GH#2915: fix ShellCheck memory explosion from language server (11 GB RSS, kernel panics)#2918

Merged
marcusquinn merged 2 commits intomainfrom
bugfix/shellcheck-memory-explosion
Mar 5, 2026
Merged

GH#2915: fix ShellCheck memory explosion from language server (11 GB RSS, kernel panics)#2918
marcusquinn merged 2 commits intomainfrom
bugfix/shellcheck-memory-explosion

Conversation

@marcusquinn
Copy link
Owner

Summary

  • Root cause: bash language server hardcodes --external-sources in every ShellCheck invocation. With 463 cross-sourcing scripts, this causes exponential AST expansion (11 GB RSS, kernel panics)
  • Fix: ShellCheck wrapper script strips --external-sources; memory monitor auto-kills runaway processes; setup.sh deploys and configures automatically
  • Activation: setup.sh / aidevops update sets SHELLCHECK_PATH via launchctl setenv (macOS GUI) + shell rc files (all terminals)

Changes

File What
.agents/scripts/shellcheck-wrapper.sh Root cause fix — strips --external-sources, enforces ulimit, finds real shellcheck on PATH
.agents/scripts/.shellcheckrc Secondary defense — external-sources=false for direct file invocations
.agents/scripts/memory-pressure-monitor.sh Safety net — auto-kills ShellCheck at >4 GB RSS or >10 min runtime
setup-modules/shell-env.sh New setup_shellcheck_wrapper() — configures SHELLCHECK_PATH cross-shell
setup.sh Calls setup_shellcheck_wrapper() in both interactive and non-interactive modes

Verification

  • Wrapper tested: exact language server invocation pattern completes instantly (was timing out at 10s+)
  • ShellCheck clean on all modified scripts
  • All 28 memory-pressure-monitor tests pass
  • --status and --help output updated with auto-kill documentation

Post-merge

After merge, run aidevops update (or ./setup.sh) to deploy the wrapper and set SHELLCHECK_PATH. Restart terminal/OpenCode for the env var to take effect.

Closes #2915

The bash language server hardcodes --external-sources in every ShellCheck
invocation. With 463 scripts cross-sourcing each other in .agents/scripts/,
this causes exponential AST expansion (observed: 11 GB RSS, kernel panics).

Three-layer defense:

1. shellcheck-wrapper.sh (root cause fix): Strips --external-sources from
   args before passing to real ShellCheck. Set as SHELLCHECK_PATH so the
   language server uses it instead of the real binary.

2. .agents/scripts/.shellcheckrc (secondary): Disables external-sources
   for direct file invocations. Limited effectiveness for stdin-piped
   content (language server pattern).

3. memory-pressure-monitor.sh auto-kill (safety net): ShellCheck processes
   hitting CRITICAL RSS (>4 GB) or exceeding runtime (>10 min) are now
   automatically killed. Safe because the language server respawns them.

Setup integration:
- setup.sh calls setup_shellcheck_wrapper() after agent deployment
- Uses launchctl setenv (macOS GUI processes) + shell rc files (terminals)
- Idempotent, runs in both interactive and non-interactive modes

Closes #2915
@github-actions
Copy link

github-actions bot commented Mar 5, 2026

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 108 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Thu Mar 5 14:43:06 UTC 2026: Code review monitoring started
Thu Mar 5 14:43:07 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 108

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 108
  • VULNERABILITIES: 0

Generated on: Thu Mar 5 14:43:09 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

Warning

Rate limit exceeded

@marcusquinn has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 15 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f5e84fda-8232-481d-b429-a0b9fcaf9475

📥 Commits

Reviewing files that changed from the base of the PR and between 9fbc675 and 779a9e6.

📒 Files selected for processing (5)
  • .agents/scripts/.shellcheckrc
  • .agents/scripts/memory-pressure-monitor.sh
  • .agents/scripts/shellcheck-wrapper.sh
  • setup-modules/shell-env.sh
  • setup.sh
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/shellcheck-memory-explosion

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical memory explosion issue with ShellCheck when used by the bash language server, which was hardcoding the --external-sources flag and leading to 11 GB RSS consumption and kernel panics due to exponential AST expansion. The solution implements a multi-layered approach: a ShellCheck wrapper script to filter out the problematic flag, an enhanced memory monitor to auto-kill runaway ShellCheck processes, and automated deployment mechanisms to ensure these fixes are applied consistently across development environments.

Highlights

  • ShellCheck Wrapper Introduced: A new shellcheck-wrapper.sh script was added to intercept ShellCheck invocations, specifically stripping the problematic --external-sources flag that caused excessive memory usage.
  • Memory Pressure Monitoring Enhanced: The memory-pressure-monitor.sh script was updated to automatically kill runaway ShellCheck processes that exceed critical RSS limits or runtime durations, acting as a safety net.
  • ShellCheck Configuration Added: A new .shellcheckrc file was created to disable external source following for direct ShellCheck invocations within the agents directory, providing a secondary defense against memory issues.
  • Automated Wrapper Deployment: The setup-modules/shell-env.sh script now includes a function to configure SHELLCHECK_PATH to use the new wrapper across various shell environments and GUI applications on macOS.
  • Setup Integration: The main setup.sh script was modified to automatically call the setup_shellcheck_wrapper function during both interactive and non-interactive setup processes.
Changelog
  • .agents/scripts/.shellcheckrc
    • Added a new configuration file to disable external source following.
    • Included specific disable rules to prevent recursive expansion.
  • .agents/scripts/memory-pressure-monitor.sh
    • Updated documentation to explain the new auto-kill feature for ShellCheck.
    • Incremented the script version to 1.1.0.
    • Introduced a new configurable environment variable AUTO_KILL_SHELLCHECK.
    • Implemented a new _auto_kill_process function for controlled process termination.
    • Integrated auto-killing logic for ShellCheck processes exceeding RSS or runtime limits.
    • Updated status and help output to reflect the auto-kill configuration.
  • .agents/scripts/shellcheck-wrapper.sh
    • Added a new executable script to act as a ShellCheck proxy.
    • Implemented logic to dynamically locate the actual ShellCheck binary.
    • Added argument parsing to remove --external-sources from ShellCheck commands.
    • Applied a virtual memory ulimit to wrapped ShellCheck processes.
  • setup-modules/shell-env.sh
    • Added a new setup_shellcheck_wrapper function to manage the deployment and configuration of the ShellCheck wrapper.
    • Configured SHELLCHECK_PATH using launchctl for macOS GUI apps and by modifying shell RC files.
    • Included checks for wrapper existence, executability, and functionality.
  • setup.sh
    • Integrated the setup_shellcheck_wrapper call into both the default and interactive setup routines.
Activity
  • Root cause of ShellCheck memory explosion identified as --external-sources flag in bash language server.
  • A multi-pronged fix has been implemented and detailed, including a wrapper, memory monitor, and setup integration.
  • Verification steps were performed, confirming the wrapper's functionality, ShellCheck cleanliness, and memory monitor test pass.
  • Documentation for --status and --help commands has been updated to reflect auto-kill features.
  • Instructions for post-merge activation of the fixes have been provided.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request provides a robust, multi-layered solution to a critical memory explosion issue caused by ShellCheck's --external-sources flag, introducing a wrapper script, a dedicated .shellcheckrc, and an enhanced memory monitor to auto-kill runaway processes. The setup scripts are also updated to deploy and configure this new mechanism. A critical vulnerability was identified in the argument filtering logic of shellcheck-wrapper.sh, which has been addressed with a code suggestion. Additionally, suggestions to improve the robustness of PATH parsing and error handling for launchctl setenv have been provided, aligning with best practices for shell scripting.

Comment on lines +92 to +94
while IFS= read -r arg; do
filtered_args+=("$arg")
done < <(_filter_args "$@")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The script uses printf '%s\n' to serialize arguments and a while read loop to deserialize them. This is vulnerable to argument splitting if any argument contains a newline character. An attacker could use this to bypass the stripping of --external-sources or -x flags, which the wrapper is specifically designed to prevent to avoid Denial of Service (memory explosion). Bypassing this control directly re-introduces the risk of 11 GB RSS usage and kernel panics as described in the PR summary.

Suggested change
while IFS= read -r arg; do
filtered_args+=("$arg")
done < <(_filter_args "$@")
local filtered_args=()
while [[ $# -gt 0 ]]; do
case "$1" in
--external-sources | -x)
# Strip this flag
;;
*)
filtered_args+=("$1")
;;
esac
shift
done

Comment on lines +37 to +39
local IFS=':'
local dir
for dir in $PATH; do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current method of iterating over PATH using for dir in $PATH is not safe as it is subject to word splitting. This will cause issues if any directory in the PATH contains spaces. Using read -a to populate an array is a more robust approach that correctly handles such edge cases.

Suggested change
local IFS=':'
local dir
for dir in $PATH; do
local -a path_dirs
IFS=':' read -r -a path_dirs <<< "$PATH"
for dir in "${path_dirs[@]}"; do

Comment on lines +495 to +497
if launchctl setenv SHELLCHECK_PATH "$wrapper_path" 2>/dev/null; then
print_info "Set SHELLCHECK_PATH via launchctl (GUI processes)"
fi

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Suppressing error output from launchctl setenv with 2>/dev/null can hide configuration failures. If the command fails (e.g., when running setup via SSH), the user isn't notified that GUI applications will lack the SHELLCHECK_PATH setting. It's better to report the failure to the user, providing more context on why it might have occurred.

Suggested change
if launchctl setenv SHELLCHECK_PATH "$wrapper_path" 2>/dev/null; then
print_info "Set SHELLCHECK_PATH via launchctl (GUI processes)"
fi
if launchctl setenv SHELLCHECK_PATH "$wrapper_path"; then
print_info "Set SHELLCHECK_PATH via launchctl (GUI processes)"
else
print_warning "Failed to set SHELLCHECK_PATH via launchctl. GUI apps may not use the wrapper. This can happen if not in a GUI session (e.g. SSH)."
fi
References
  1. Avoid using '2>/dev/null' for blanket suppression of command errors in shell scripts to ensure that authentication, syntax, or system issues remain visible for debugging.

Use 'while read -d :' instead of 'local IFS=:' to iterate PATH entries.
Functionally identical but avoids Codacy's SC-style warning about IFS
affecting variable expansion.
@github-actions
Copy link

github-actions bot commented Mar 5, 2026

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 108 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Thu Mar 5 14:48:23 UTC 2026: Code review monitoring started
Thu Mar 5 14:48:23 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 108

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 108
  • VULNERABILITIES: 0

Generated on: Thu Mar 5 14:48:26 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 5, 2026

@marcusquinn marcusquinn merged commit 43da347 into main Mar 5, 2026
12 checks passed
@marcusquinn marcusquinn deleted the bugfix/shellcheck-memory-explosion branch March 5, 2026 14:50
@marcusquinn
Copy link
Owner Author

Follow-up: root cause removed in PR #2939

This PR contained the wrapper, but the root cause (source-path=SCRIPTDIR in .shellcheckrc) remained. PR #2939 removes it entirely and disables SC1091 globally. The wrapper remains as defense-in-depth.

Upstream hazard note: The bash-language-server still hardcodes --external-sources — other opencode/shellcheck users with source-path in their .shellcheckrc will hit the same issue. See anomalyco/opencode#16209.

@opencode-agent
Copy link

opencode-agent bot commented Mar 5, 2026

fatal: couldn't find remote ref bugfix/shellcheck-memory-explosion

opencode session  |  github run

marcusquinn added a commit that referenced this pull request Mar 5, 2026
…oot survival

Two changes to prevent kernel panics from runaway processes:

1. Pulse plist: RunAtLoad=true so the supervisor pulse restarts after
   reboot/panic. Previously RunAtLoad=false meant the pulse never
   restarted, leaving the system unmonitored after crashes.

2. Process guard: Added to setup.sh so all users get it on install/update.
   Previously the guard plist was manually created and not managed by
   setup. Now installs automatically:
   - macOS: launchd plist, 30s interval (was 120s), RunAtLoad=true
   - Linux: cron entry, every minute (cron minimum granularity)
   - No consent needed (safety net, not autonomous action)
   - Kills ShellCheck processes >512MB RSS or >120s runtime
   - Kills other AI processes >8GB RSS or >2h runtime

Context: 5 kernel panics in one day from watchdog timeouts caused by
ShellCheck memory bloat (2GB+ per instance) exhausting the compressor
segment limit. The shellcheck-wrapper.sh (PR #2918) fixes the root
cause; this ensures the guard catches any remaining edge cases.
marcusquinn added a commit that referenced this pull request Mar 6, 2026
…oot survival (#2940)

* fix: add process guard to setup.sh and enable pulse RunAtLoad for reboot survival

Two changes to prevent kernel panics from runaway processes:

1. Pulse plist: RunAtLoad=true so the supervisor pulse restarts after
   reboot/panic. Previously RunAtLoad=false meant the pulse never
   restarted, leaving the system unmonitored after crashes.

2. Process guard: Added to setup.sh so all users get it on install/update.
   Previously the guard plist was manually created and not managed by
   setup. Now installs automatically:
   - macOS: launchd plist, 30s interval (was 120s), RunAtLoad=true
   - Linux: cron entry, every minute (cron minimum granularity)
   - No consent needed (safety net, not autonomous action)
   - Kills ShellCheck processes >512MB RSS or >120s runtime
   - Kills other AI processes >8GB RSS or >2h runtime

Context: 5 kernel panics in one day from watchdog timeouts caused by
ShellCheck memory bloat (2GB+ per instance) exhausting the compressor
segment limit. The shellcheck-wrapper.sh (PR #2918) fixes the root
cause; this ensures the guard catches any remaining edge cases.

* fix: address review feedback — dynamic PATH, HOME env, quoted cron paths, remove stderr suppression

- Use dynamic ${PATH} and add ${HOME} to guard plist env vars for
  consistency with the pulse plist pattern (CodeRabbit, Gemini)
- Remove 2>/dev/null from launchctl unload/load — || true handles
  errors, stderr aids debugging (Gemini)
- Quote paths in cron entry to handle spaces in paths (Gemini)
- Always regenerate cron entry to pick up config changes, matching
  macOS plist behavior (CodeRabbit)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: bash language server spawns unbounded ShellCheck processes (11 GB RAM, crash risk)

1 participant