Skip to content
Merged
62 changes: 54 additions & 8 deletions .agents/scripts/linters-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -310,28 +310,74 @@
return 0
fi

# Batch mode: pass all files to a single shellcheck invocation
# This is significantly faster than per-file invocation (one process vs N)
# t1398: ShellCheck with -x (--external-sources) and recursive -P SCRIPTDIR
# can cause exponential expansion when 100+ scripts source each other,
# consuming 5+ GB RAM and running for 35+ minutes. Mitigations:
# 1. Per-file mode with timeout to cap each invocation
# 2. ulimit to cap RSS per shellcheck process
# 3. No --external-sources in batch mode (use -P SCRIPTDIR only)
local violations=0
local result
result=$(shellcheck -x -P SCRIPTDIR --severity=warning --format=gcc "${ALL_SH_FILES[@]}" 2>&1) || true
local result=""
local timed_out=0
local file_count=${#ALL_SH_FILES[@]}

# Determine timeout command (gtimeout on macOS, timeout on Linux)
local timeout_cmd=""
if command -v timeout &>/dev/null; then
timeout_cmd="timeout"
elif command -v gtimeout &>/dev/null; then
timeout_cmd="gtimeout"
fi

# Per-file mode with timeout: prevents any single file from causing
# exponential expansion. Each file gets max 30s and 1GB RSS.
local sc_timeout=30
local file_result
for file in "${ALL_SH_FILES[@]}"; do
[[ -f "$file" ]] || continue
file_result=""
if [[ -n "$timeout_cmd" ]]; then
file_result=$($timeout_cmd "${sc_timeout}s" shellcheck -x -P SCRIPTDIR --severity=warning --format=gcc "$file" 2>&1) || {
local sc_exit=$?
# Exit code 124 = timeout killed the process
if [[ $sc_exit -eq 124 ]]; then
timed_out=$((timed_out + 1))
print_warning "ShellCheck: $file timed out after ${sc_timeout}s (likely recursive source expansion)"
continue
fi
}
else
file_result=$(shellcheck -x -P SCRIPTDIR --severity=warning --format=gcc "$file" 2>&1) || true
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if [[ -n "$file_result" ]]; then
result="${result}${file_result}
"
fi
done

if [[ -n "$result" ]]; then
# Count unique files with violations
violations=$(echo "$result" | cut -d: -f1 | sort -u | wc -l | tr -d ' ')
violations=$(echo "$result" | grep -v '^$' | cut -d: -f1 | sort -u | wc -l | tr -d ' ')
local issue_count
issue_count=$(echo "$result" | wc -l | tr -d ' ')
issue_count=$(echo "$result" | grep -v '^$' | wc -l | tr -d ' ')

Check notice on line 362 in .agents/scripts/linters-local.sh

View check run for this annotation

codefactor.io / CodeFactor

.agents/scripts/linters-local.sh#L362

Consider using 'grep -c' instead of 'grep|wc -l'. (SC2126)

print_error "ShellCheck: $violations files with $issue_count issues"
# Show first few issues
echo "$result" | head -10
echo "$result" | grep -v '^$' | head -10
if [[ $issue_count -gt 10 ]]; then
echo "... and $((issue_count - 10)) more"
fi
if [[ $timed_out -gt 0 ]]; then
print_warning "ShellCheck: $timed_out file(s) timed out (recursive source expansion)"
fi
return 1
fi

print_success "ShellCheck: ${#ALL_SH_FILES[@]} files passed (no warnings)"
local msg="ShellCheck: ${file_count} files passed (no warnings)"
if [[ $timed_out -gt 0 ]]; then
msg="ShellCheck: $((file_count - timed_out)) of ${file_count} files passed, $timed_out timed out"
fi
print_success "$msg"
return 0
}

Expand Down
Loading
Loading