Skip to content

fix: guard possibly-empty array expansions so bash 3.2 can't fake a reviewer cli-error - #139

Merged
atomantic merged 3 commits into
mainfrom
claim/issue-138
Aug 12, 2026
Merged

fix: guard possibly-empty array expansions so bash 3.2 can't fake a reviewer cli-error#139
atomantic merged 3 commits into
mainfrom
claim/issue-138

Conversation

@atomantic

@atomantic atomantic commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Summary

On a machine with neither timeout nor gtimeoutstock macOS, where timeout ships only with GNU coreutils and gtimeout only via Homebrew — TIMEOUT_CMD is legitimately empty. Under bash 3.2 (still /bin/bash on macOS) a bare "${ARR[@]}" on an empty array is an unset expansion, so with set -u it aborts with unbound variable before the reviewer process ever starts.

The failure is indistinguishable from a genuine reviewer failure: every file returns RC=1 with empty output, the loop counts them all in REVIEW_ERRORS, and the pass resolves to cli-error — a hard error that ~opt does not excuse — so the merge is blocked on a PR no reviewer ever looked at.

Verified on the actual GNU bash, version 3.2.57(1)-release:

$ /bin/bash -c 'set -u; A=(); "${A[@]}" echo ok'
/bin/bash: A[@]: unbound variable     # RC=127

$ /bin/bash -c 'set -u; A=(); ${A[@]+"${A[@]}"} echo ok'
ok                                    # RC=0

The guarded form also preserves quoting when the array is non-empty, and behaves identically under zsh.

Changes

  • lib/empty-array-expansion.md (new) — the rule, the bash-3.2 mechanism, why an absent timeout/gtimeout is a supported configuration rather than a reviewer failure, and how to recognize the false cli-error when it appears. One partial, following the lib/gh-host.md convention, because the alternative is the same paragraph in three files drifting apart.
  • lib/ollama-review-loop.md — the ollama run invocation now expands both TIMEOUT_CMD and OLLAMA_FLAGS as ${ARR[@]+"${ARR[@]}"}. The total-failure branch now rules out the shell-expansion false positive first: all-files-errored with only unbound variable in $ERR_FILE is an environment condition to report and re-run, not cli-error.
  • lib/local-agent-review-loop.md — same expansion fix for TIMEOUT_CMD and MODEL_FLAG (both can legitimately be empty). The step-2 note that claimed "an empty array expands to zero words in bash and zsh alike" was the load-bearing wrong statement; it now says the guard is required, not decoration.
  • lib/enhance-loop.md — same fix for TIMEOUT_CMD and MODEL_FLAG.
  • test/review-loop-contract.test.js — two contract tests. The first scans the three loop partials by pattern (not a hardcoded array-name list, so a newly introduced optional-argument array is covered the day it lands) and rejects both an unguarded "${SOMEARRAY[@]}" and the double-wrapped ${A[@]+${A[@]+"${A[@]}"}} slip. The second asserts the shared partial carries the contract and that each loop links to it, deliberately not pinning the loops' own prose wording.
  • install.sh / uninstall.sh — register the new partial in the LIBS allowlist (the npm installer enumerates lib/ dynamically; the curl installers do not). The existing allowlist test caught this.

Audit of the other partials

A repo-wide sweep for [@]} found exactly one remaining unguarded expansion: LIST_ARGS in commands/do/next.md:204. Left as-is deliberately — it is initialized LIST_ARGS=(--state open) and only ever appended to, so it can never be empty and has no failure mode here.

Test plan

  • npm test — 187/187 pass (185 before, +2 new contract tests)
  • Both new tests confirmed to actually bite: the pattern scan flagged a real double-wrap introduced by the first commit's mechanical rewrite, and the LIBS allowlist test caught the unregistered new partial
  • Guarded-form behavior verified on the real macOS bash 3.2 and on zsh — empty and non-empty, including an argument containing a space
  • Reviewed with agy (antigravity); its findings matched the self-review pass and are applied in 967b0b2

Closes #138

…eviewer cli-error (#138)

Stock macOS ships neither timeout(1) nor gtimeout, so TIMEOUT_CMD is legitimately
empty there. Under /bin/bash 3.2 a bare "${ARR[@]}" on an empty array is an unset
expansion, so with set -u it aborts with 'unbound variable' before the reviewer ever
starts. Every file then returns RC=1 with empty output, the loop counts them all as
REVIEW_ERRORS, and the pass resolves to cli-error -- a hard error that ~opt does not
excuse -- blocking the merge on a PR no reviewer looked at.

Switch TIMEOUT_CMD, MODEL_FLAG, and OLLAMA_FLAGS to the ${ARR[@]+"${ARR[@]}"} form
across the ollama, local-agent, and enhance loops; document that an absent timeout
binary is the common macOS case and a supported configuration rather than a reviewer
failure; and add contract tests that fail if an unguarded expansion returns.
…nd hoist the rule into one partial

The mechanical rewrite in the previous commit wrapped three already-guarded
expansions a second time, publishing a competing spelling of the very rule this
change exists to teach. Collapse them, and move the five near-identical copies of
the explanation into lib/empty-array-expansion.md (the lib/gh-host.md convention)
so the next amendment can't land in two files and rot in three.

The contract test now scans by pattern instead of a hardcoded array-name list, so a
newly introduced optional-argument array is covered the day it lands, and it also
rejects the double-wrap slip. Its companion no longer pins the loops' prose wording
-- it asserts the shared partial carries the contract and that each loop links to it.

Registering the new partial in install.sh/uninstall.sh was caught by the existing
curl-installer LIBS allowlist test.
…ed guard too

${A[@]+"${A[@]+…}"} is the same slip as ${A[@]+${A[@]+…}} but slipped past the
detector, so the contract test allowed exactly one spelling of the mistake it exists
to reject.
@atomantic
atomantic merged commit 00e9b37 into main Aug 12, 2026
4 checks passed
@atomantic
atomantic deleted the claim/issue-138 branch August 12, 2026 23:22
@atomantic atomantic mentioned this pull request Aug 12, 2026
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.

ollama review loop reports a false cli-error (and blocks merge) when timeout/gtimeout is absent under bash 3.2

1 participant