Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 73 additions & 15 deletions setup-modules/shell-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -458,18 +458,26 @@ add_local_bin_to_path() {
return 0
}

# GH#2915: Configure SHELLCHECK_PATH to use the safe wrapper that strips
# --external-sources. The bash language server hardcodes --external-sources
# in every ShellCheck invocation, causing exponential memory growth (11 GB+)
# when source chains span 463+ scripts. The wrapper intercepts this.
# GH#2915, GH#2993: Ensure all processes use the safe ShellCheck wrapper.
#
# Three layers ensure all processes see the wrapper:
# The bash language server hardcodes --external-sources in every ShellCheck
# invocation, causing exponential memory growth (9+ GB) when source chains
# span 463+ scripts. The wrapper strips --external-sources and enforces ulimit.
#
# GH#2915 set SHELLCHECK_PATH env var, but bash-language-server ignores it —
# it resolves `shellcheck` via PATH lookup, finding /opt/homebrew/bin/shellcheck
# directly. GH#2993 fixes this by placing a shim on PATH ahead of the real binary.
#
# Four layers ensure all processes use the wrapper:
# 0. PATH shim — ~/.aidevops/bin/shellcheck symlink + PATH prepend (GH#2993)
# 1. launchctl setenv (macOS) — GUI-launched apps (current boot only)
# 2. .zshenv — ALL zsh processes including non-interactive (persists)
# 3. Shell rc files (.zshrc, .bash_profile) — interactive terminals
# Layer 2 is critical: opencode spawns bash-language-server as a non-interactive
# child process. .zshrc is NOT sourced for non-interactive shells, so without
# .zshenv the wrapper is invisible to the LSP and shellcheck runs unbounded.
#
# Layer 0 is the primary fix: bash-language-server does a PATH lookup for
# "shellcheck". By placing ~/.aidevops/bin first on PATH with a symlink to
# the wrapper, the language server finds the wrapper instead of the real binary.
# Layers 1-3 are retained for tools that honour SHELLCHECK_PATH.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
setup_shellcheck_wrapper() {
local wrapper_path="$HOME/.aidevops/agents/scripts/shellcheck-wrapper.sh"

Expand All @@ -492,9 +500,39 @@ setup_shellcheck_wrapper() {
local env_line
# shellcheck disable=SC2016 # env_line is written to rc files; must expand at shell startup
env_line='export SHELLCHECK_PATH="$HOME/.aidevops/agents/scripts/shellcheck-wrapper.sh"'
# shellcheck disable=SC2016 # path_line is written to rc files; must expand at shell startup
local path_line='export PATH="$HOME/.aidevops/bin:$PATH"'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
local added_to=""
local already_in=""

# Layer 0: PATH shim (GH#2993)
# Create ~/.aidevops/bin/shellcheck as a symlink to the wrapper.
# This is the primary fix: bash-language-server resolves `shellcheck` via
# PATH, so the symlink must appear on PATH before /opt/homebrew/bin.
local shim_dir="$HOME/.aidevops/bin"
local shim_path="$shim_dir/shellcheck"
mkdir -p "$shim_dir"

# Create or update the symlink
local wrapper_realpath
wrapper_realpath="$(realpath "$wrapper_path" 2>/dev/null || readlink -f "$wrapper_path" 2>/dev/null || echo "$wrapper_path")"
if [[ -L "$shim_path" ]]; then
local current_target
current_target="$(realpath "$shim_path" 2>/dev/null || readlink -f "$shim_path" 2>/dev/null || echo "")"
if [[ "$current_target" != "$wrapper_realpath" ]]; then
ln -sf "$wrapper_path" "$shim_path"
print_info "Updated shellcheck shim symlink: $shim_path → $wrapper_path"
fi
elif [[ -e "$shim_path" ]]; then
# Regular file exists — back up and replace with symlink
mv "$shim_path" "${shim_path}.bak.$(date +%Y%m%d_%H%M%S)"
ln -sf "$wrapper_path" "$shim_path"
print_info "Replaced shellcheck shim with symlink: $shim_path → $wrapper_path"
else
ln -sf "$wrapper_path" "$shim_path"
print_success "Created shellcheck shim: $shim_path → $wrapper_path"
fi

# Layer 1: launchctl setenv (macOS) — affects all GUI-launched processes
if [[ "$PLATFORM_MACOS" == "true" ]]; then
if launchctl setenv SHELLCHECK_PATH "$wrapper_path" 2>/dev/null; then
Expand All @@ -506,17 +544,30 @@ setup_shellcheck_wrapper() {
# This is critical because opencode spawns bash-language-server as a
# non-interactive child process. .zshrc is NOT sourced for non-interactive
# shells, so SHELLCHECK_PATH set only in .zshrc is invisible to the LSP.
# GH#2993: Also prepend ~/.aidevops/bin to PATH here so the shim is found.
local zshenv="$HOME/.zshenv"
if [[ -f "$zshenv" ]] || command -v zsh >/dev/null 2>&1; then
touch "$zshenv"

# SHELLCHECK_PATH env var (for tools that honour it)
if grep -q 'SHELLCHECK_PATH' "$zshenv" 2>/dev/null; then
already_in="${already_in:+$already_in, }$zshenv"
else
touch "$zshenv"
echo "" >>"$zshenv"
echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)" >>"$zshenv"
echo "$env_line" >>"$zshenv"
added_to="${added_to:+$added_to, }$zshenv"
fi

# PATH prepend for ~/.aidevops/bin (GH#2993: shim must be on PATH)
if ! grep -q '\.aidevops/bin' "$zshenv" 2>/dev/null; then
echo "" >>"$zshenv"
echo "# Added by aidevops setup (GH#2993: shellcheck shim on PATH)" >>"$zshenv"
echo "$path_line" >>"$zshenv"
print_success "Prepended $shim_dir to PATH in .zshenv"
else
print_info "$shim_dir already on PATH in .zshenv"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The script calls touch "$zshenv" before this block, and set -e is active, which should ensure the file exists. Suppressing stderr with 2>/dev/null can hide important errors like read permissions issues. To align with the project's general rules on error visibility for debugging, please remove 2>/dev/null from the grep commands.

Suggested change
if grep -q 'SHELLCHECK_PATH' "$zshenv" 2>/dev/null; then
already_in="${already_in:+$already_in, }$zshenv"
else
touch "$zshenv"
echo "" >>"$zshenv"
echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)" >>"$zshenv"
echo "$env_line" >>"$zshenv"
added_to="${added_to:+$added_to, }$zshenv"
fi
# PATH prepend for ~/.aidevops/bin (GH#2993: shim must be on PATH)
if ! grep -q '\.aidevops/bin' "$zshenv" 2>/dev/null; then
echo "" >>"$zshenv"
echo "# Added by aidevops setup (GH#2993: shellcheck shim on PATH)" >>"$zshenv"
echo "$path_line" >>"$zshenv"
print_success "Prepended $shim_dir to PATH in .zshenv"
else
print_info "$shim_dir already on PATH in .zshenv"
fi
if grep -q 'SHELLCHECK_PATH' "$zshenv"; then
already_in="${already_in:+$already_in, }$zshenv"
else
echo "" >>"$zshenv"
echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)" >>"$zshenv"
echo "$env_line" >>"$zshenv"
added_to="${added_to:+$added_to, }$zshenv"
fi
# PATH prepend for ~/.aidevops/bin (GH#2993: shim must be on PATH)
if ! grep -q '\.aidevops/bin' "$zshenv"; then
echo "" >>"$zshenv"
echo "# Added by aidevops setup (GH#2993: shellcheck shim on PATH)" >>"$zshenv"
echo "$path_line" >>"$zshenv"
print_success "Prepended $shim_dir to PATH in .zshenv"
else
print_info "$shim_dir already on PATH in .zshenv"
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.

fi

# Layer 3: Shell rc files — affects interactive terminal sessions
Expand All @@ -529,16 +580,22 @@ setup_shellcheck_wrapper() {
touch "$rc_file"
fi

# Check if already added
# SHELLCHECK_PATH env var
if grep -q 'SHELLCHECK_PATH' "$rc_file" 2>/dev/null; then
already_in="${already_in:+$already_in, }$rc_file"
continue
else
echo "" >>"$rc_file"
echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)" >>"$rc_file"
echo "$env_line" >>"$rc_file"
added_to="${added_to:+$added_to, }$rc_file"
fi

echo "" >>"$rc_file"
echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)" >>"$rc_file"
echo "$env_line" >>"$rc_file"
added_to="${added_to:+$added_to, }$rc_file"
# PATH prepend for ~/.aidevops/bin (GH#2993)
if ! grep -q '\.aidevops/bin' "$rc_file" 2>/dev/null; then
echo "" >>"$rc_file"
echo "# Added by aidevops setup (GH#2993: shellcheck shim on PATH)" >>"$rc_file"
echo "$path_line" >>"$rc_file"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Similar to the .zshenv block, touch "$rc_file" is called before this, ensuring the file exists. To improve debuggability by making potential read errors visible, please remove 2>/dev/null from the grep commands, in accordance with the project's general rules.

Suggested change
if grep -q 'SHELLCHECK_PATH' "$rc_file" 2>/dev/null; then
already_in="${already_in:+$already_in, }$rc_file"
continue
else
echo "" >>"$rc_file"
echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)" >>"$rc_file"
echo "$env_line" >>"$rc_file"
added_to="${added_to:+$added_to, }$rc_file"
fi
echo "" >>"$rc_file"
echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)" >>"$rc_file"
echo "$env_line" >>"$rc_file"
added_to="${added_to:+$added_to, }$rc_file"
# PATH prepend for ~/.aidevops/bin (GH#2993)
if ! grep -q '\.aidevops/bin' "$rc_file" 2>/dev/null; then
echo "" >>"$rc_file"
echo "# Added by aidevops setup (GH#2993: shellcheck shim on PATH)" >>"$rc_file"
echo "$path_line" >>"$rc_file"
fi
if grep -q 'SHELLCHECK_PATH' "$rc_file"; then
already_in="${already_in:+$already_in, }$rc_file"
else
echo "" >>"$rc_file"
echo "# Added by aidevops setup (GH#2915: prevent ShellCheck memory explosion)" >>"$rc_file"
echo "$env_line" >>"$rc_file"
added_to="${added_to:+$added_to, }$rc_file"
fi
# PATH prepend for ~/.aidevops/bin (GH#2993)
if ! grep -q '\.aidevops/bin' "$rc_file"; then
echo "" >>"$rc_file"
echo "# Added by aidevops setup (GH#2993: shellcheck shim on PATH)" >>"$rc_file"
echo "$path_line" >>"$rc_file"
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.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
done < <(get_all_shell_rcs)

if [[ -n "$added_to" ]]; then
Expand All @@ -556,6 +613,7 @@ setup_shellcheck_wrapper() {

# Also export for current session
export SHELLCHECK_PATH="$wrapper_path"
export PATH="$HOME/.aidevops/bin:$PATH"

return 0
}
Expand Down
Loading