#!/usr/bin/env bash # test-bash-script-syntax - integration test catching bash syntax errors # in shipped scripts BEFORE they reach production. # # Real catch: 2026-06-12 12:00 UTC `bend-emit-pool.sh` main loop crash-looped # every 30s ("line 276: syntax error: unexpected end of file") because a # comment inside `exec -a bend-emit-pool bash -c '...'` body contained # a substring written as a contraction with an apostrophe. A single-quote # inside that bash -c body closed our outer wrapper early. Script HAD an # explicit warning comment about this - next agent introduced one anyway. # # Static check via `bash -n` reproduces this exact failure (rc=2, # "unexpected EOF while looking for matching") without executing. # Running this gate in CI would have failed our commit pre-production. # # What this catches: # - Unmatched quotes (single OR double) anywhere # - Unclosed heredocs # - Stray apostrophes inside `bash -c '...'` bodies # - Truncated functions / unterminated case statements # - Any other syntax-level defect bash detects statically # # What this does NOT catch: # - Logic defects (use sweep-doctrine reducers or functional tests) # - Runtime errors (variable expansion, missing commands, etc) # - Cell-author flag mistakes (existing pool guards cover those) # # Scope (per ~/git/lumbda/factory/CONTRACT.md): # - Always scans $LUMBDA_FACTORY_DIR/*.sh + $LUMBDA_FACTORY_DIR/tests/integration/*.sh # - If $LUMBDA_DOMAIN_DIR is set, also scans $LUMBDA_DOMAIN_DIR/scripts/*.sh # # Exit: 0 if every .sh parses clean; non-zero with a per-file report otherwise. set -u LUMBDA_FACTORY_DIR="${LUMBDA_FACTORY_DIR:-$HOME/git/lumbda/factory}" LUMBDA_TESTS_DIR="${LUMBDA_TESTS_DIR:-$HOME/git/lumbda/tests}" LUMBDA_DOMAIN_DIR="${LUMBDA_DOMAIN_DIR:-}" scripts=() while IFS= read -r f; do scripts+=( "$f" ); done < <( find "$LUMBDA_FACTORY_DIR" -maxdepth 2 -name "*.sh" -type f 2>/dev/null find "$LUMBDA_TESTS_DIR/integration" -maxdepth 1 -name "*.sh" -type f 2>/dev/null find "$LUMBDA_TESTS_DIR/sweep-doctrine" -maxdepth 1 -name "*.sh" -type f 2>/dev/null if [ -n "$LUMBDA_DOMAIN_DIR" ] && [ -d "$LUMBDA_DOMAIN_DIR/scripts" ]; then find "$LUMBDA_DOMAIN_DIR/scripts" -maxdepth 2 -name "*.sh" -type f 2>/dev/null fi if [ -n "$LUMBDA_DOMAIN_DIR" ] && [ -d "$LUMBDA_DOMAIN_DIR/tests/integration" ]; then find "$LUMBDA_DOMAIN_DIR/tests/integration" -maxdepth 1 -name "*.sh" -type f 2>/dev/null fi ) if [ "${#scripts[@]}" -eq 0 ]; then echo "ERROR: no .sh files found under $LUMBDA_FACTORY_DIR${LUMBDA_DOMAIN_DIR:+ or $LUMBDA_DOMAIN_DIR/scripts}" exit 1 fi echo "[bash-syntax] checking ${#scripts[@]} script(s)" errfile=$(mktemp /tmp/bash-n-err.XXXXXX) trap 'rm -f "$errfile"' EXIT fail_count=0 for s in "${scripts[@]}"; do if bash -n "$s" 2>"$errfile"; then : # pass - loud only on failure else fail_count=$((fail_count + 1)) echo "FAIL $s:" sed 's/^/ /' "$errfile" fi done if [ "$fail_count" -gt 0 ]; then echo "" echo "[bash-syntax] FAIL - $fail_count script(s) failed bash -n" echo "Common cause: apostrophe inside an exec -a NAME bash -c ..." echo "wrapper closes our outer single-quote early. Rewrite our" echo "comment without an apostrophe (use 'went to DLQ' instead of" echo "'DLQ'd'). See bend-emit-pool.sh feb0408 for post-mortem." exit 1 fi echo "[bash-syntax] PASS - all ${#scripts[@]} script(s) parse clean" exit 0