From 437cc1d3792807b84ea0a6a026c091db3c6adbc1 Mon Sep 17 00:00:00 2001 From: Claude Code Bot Date: Fri, 11 Sep 2026 15:06:05 -0700 Subject: [PATCH] fix(standards): stop passing a stray empty argument when scope is unset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `"${scope_args[@]-}"` expands an EMPTY array to one empty-string argument rather than to nothing. run-standards.sh then hits that empty string in its catch-all `*)` branch, reports `unknown argument: ` and exits 2. The effect is that standards-check fails on any repo that requests no `changed-since` narrowing — which is every repo running the check in whole-repo hygiene mode. The failure surfaced on nightowlstudiollc/reliquarist #93-96 after a rebase, but it is not specific to those PRs or to Dependabot. Plain `"${scope_args[@]}"` expands to nothing when the array is empty and is safe under `set -u` on bash 4.4+; runners are bash 5.x. Both forms behave identically when the array is non-empty, so narrowing is unaffected. Note that `--skip ""` is NOT the cause: run-standards.sh accepts an empty `--skip` value without complaint. Verified by running the real runner with both expansion forms — the old form produces one `unknown argument` hit, the new form produces zero. The regression test asserts on argument assembly rather than lint output, so it does not depend on linters being installed, and it carries a paired assertion that the known-bad form still reproduces — without which the first assertion could silently become vacuous. Advances #116 context: standards-check is now the sole required check on four repos, so this failure blocks all merges there until fixed. Claude-Session: https://claude.ai/code/session_01ESsw699T54JHARkQXrdL3o --- .github/workflows/standards-check.yml | 7 ++++- tests/test-run-standards.sh | 39 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/.github/workflows/standards-check.yml b/.github/workflows/standards-check.yml index 534eeaa..b001919 100644 --- a/.github/workflows/standards-check.yml +++ b/.github/workflows/standards-check.yml @@ -236,6 +236,11 @@ jobs: # entirely rather than passed with an empty value — the script # treats an unresolvable ref as a hard error, which is correct for a # typo'd ref but wrong for "no narrowing requested". + # "${scope_args[@]-}" expands an EMPTY array to one empty-string + # argument, not to nothing, which run-standards.sh then rejects via + # its catch-all `*)` branch as `unknown argument: `. Plain + # "${scope_args[@]}" expands to nothing when empty and is safe under + # `set -u` on bash 4.4+ (runners are bash 5.x). scope_args=() [ -n "${CHANGED_SINCE}" ] && scope_args+=(--changed-since "${CHANGED_SINCE}") bash standards-src/standards/run-standards.sh \ @@ -243,4 +248,4 @@ jobs: --config-dir standards-src/standards \ --skip "${skip_list}" \ --node-floor "${NODE_FLOOR}" \ - "${scope_args[@]-}" + "${scope_args[@]}" diff --git a/tests/test-run-standards.sh b/tests/test-run-standards.sh index 3ecf969..8678388 100755 --- a/tests/test-run-standards.sh +++ b/tests/test-run-standards.sh @@ -159,5 +159,44 @@ else _bad "--changed-since bad ref exited ${rc}, expected 2 (see ${tmp}/cs-badref.log)" fi +# The workflow builds the optional --changed-since flag in an array and +# expands it at the call site. "${arr[@]-}" expands an EMPTY array to one +# empty-string argument rather than to nothing, which lands on the runner's +# catch-all `*)` branch as `unknown argument: ` and exits 2 — so a repo that +# skips no linters and requests no narrowing fails the whole check. Assert the +# expansion form the workflow actually uses passes no stray argument. +# This asserts on argument assembly only, so it does not depend on the linters +# being installed — it counts what the call site would pass, without running a +# real lint. The workflow's own expansion form is reproduced verbatim. +_argc_for() { # $1 = expansion form, literally as written in the workflow + bash -c ' + set -euo pipefail + scope_args=() + CHANGED_SINCE="" + [ -n "${CHANGED_SINCE}" ] && scope_args+=(--changed-since "${CHANGED_SINCE}") + set -- --skip "" --node-floor 22 '"$1"' + echo "$#" + ' +} +# Built from a literal dollar rather than written inline, so the expansion +# forms under test stay unexpanded here without tripping SC2016. +d='\044' +fixed_form="$(printf '"%b{scope_args[@]}"' "${d}")" +broken_form="$(printf '"%b{scope_args[@]-}"' "${d}")" +fixed_argc="$(_argc_for "${fixed_form}")" +broken_argc="$(_argc_for "${broken_form}")" +if [[ "${fixed_argc}" -eq 4 ]]; then + _ok "empty scope_args expands to nothing (no stray empty argument)" +else + _bad "empty scope_args expanded to ${fixed_argc} args, expected 4" +fi +# Guard the test itself: the form this replaced must still be detectably wrong, +# so a future refactor cannot make this assertion vacuous. +if [[ "${broken_argc}" -eq 5 ]]; then + _ok "known-bad expansion form is still detected as passing a stray argument" +else + _bad "known-bad expansion form no longer reproduces; this test proves nothing" +fi + echo "${pass} passed, ${fail} failed" [[ "${fail}" -eq 0 ]]