diff --git a/corpus/DIVERGENCES.md b/corpus/DIVERGENCES.md index 615aefc..3abde2c 100644 --- a/corpus/DIVERGENCES.md +++ b/corpus/DIVERGENCES.md @@ -10,6 +10,34 @@ stays open until fixed in the fork (or upstream) and re-verified by the corpus. ## Closed (recent) +### D3 — errexit re-triggered by a compound command's aggregated status (fixed in fork) + +- **Found**: narOS N2 first agent-tier CI build (run 29669891162): with + `/bin/sh → nash` baked into naros-base, trixie's `tzdata` postinst exited 1 + under dpkg configure, cascading into unconfigured python3/nodejs and failing + the whole `naros-agent` image build — exactly the loud-in-CI dogfood failure + mode the agent Dockerfile courts on purpose. +- **Repro**: `set -e; if true; then false && true; fi; echo hi` → bash prints + `hi` (exit 0), nash exited 1. In tzdata's postinst the trigger was + `which restorecon >/dev/null 2>&1 && restorecon …` (restorecon absent) as + the last statement of an `if` body under `set -e`. +- **Root cause**: brush applies errexit at `Pipeline::execute`, and an + `if`/brace-group/`case`/`for` compound is itself a (single-command) pipeline + — so a failure that was already exempt *inside* the compound (short-circuited + AND-OR list, `!`-negated pipeline) re-triggered errexit on the compound's + aggregated status. bash never re-adjudicates a grouping/looping compound's + status; it does re-trigger for simple commands (incl. function calls), + subshells, `[[ ]]`, and `(( ))`. +- **Fix**: `errexit_applies_to_pipeline` in `brush-core/src/interp.rs` — the + pipeline-level errexit/ERR-trap application now fires only for multi-command + pipelines, simple commands, subshells, extended tests, and arithmetic + commands. Verified against bash on a 15-case matrix (if/brace/case/for/while + bodies, function calls, subshells, cmdsub assignment, `!`, `||`, pipe-to-cat, + `[[ ]]`, compound redirect failures) — all matching; trixie tzdata postinst + green under nash; corpus 101/101 = 100%; candidate for upstreaming. +- **Regression tests**: corpus `errexit-if-andlist`, `errexit-brace-andlist`, + `errexit-if-bang`, `errexit-fn-status`. + ### D2 — non-ASCII bytes re-encoded through `read`/`echo` under C/empty locale (fixed in fork) - **Found**: narOS N0 rootfs validation (first real-world install run under the diff --git a/corpus/corpus.jsonl b/corpus/corpus.jsonl index 52d5f3c..1b017e5 100644 --- a/corpus/corpus.jsonl +++ b/corpus/corpus.jsonl @@ -95,3 +95,7 @@ {"id": "utf8-read-passthru", "cmd": "printf 'F\\305\\221tan\\303\\272s\\303\\255tv\\303\\241ny\\n' | { read x; printf '%s\\n' \"$x\"; }"} {"id": "utf8-while-read-file", "cmd": "printf 'caf\\303\\251.crt\\nna\\303\\257ve.pem\\n' > names.txt; while read n; do echo \"got:$n\"; done < names.txt"} {"id": "utf8-cmdsub-roundtrip", "cmd": "x=$(printf '\\303\\251l\\303\\251gant'); echo \"$x\""} +{"id": "errexit-if-andlist", "cmd": "set -e; if true; then false && true; fi; echo survived"} +{"id": "errexit-brace-andlist", "cmd": "set -e; { false && true; }; echo grouped-ok"} +{"id": "errexit-if-bang", "cmd": "set -e; if true; then ! true; fi; echo bang-ok"} +{"id": "errexit-fn-status", "cmd": "set -e; f() { false && true; }; f && echo unreachable || echo fn-failed"}