From b74e052bec558100993379de19d49bf9ddbcdb1c Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Thu, 3 Sep 2026 05:23:23 +0100 Subject: [PATCH 1/2] fix: stop build_escript() writing mix output into the caller's stdout `build_escript()` corrupts the scan payload whenever it runs. Callers use the documented CI recipe: HYPATIA_FORMAT=json hypatia-cli.sh scan . --exit-zero > hypatia-findings.json Two lines then write build chatter to stdout, i.e. into that JSON file: 1. `mix deps.get --quiet 2>/dev/null || true` silences stderr but leaves stdout pointed at the payload. 2. `mix escript.build 2>&1 >&2` has its redirections BACKWARDS. They apply left to right: `2>&1` points fd2 at wherever fd1 currently is -- the caller's findings.json -- and `>&2` then points fd1 at that same file. Both streams land in the payload. The intended idiom is `>&2 2>&1`. The fix redirects the whole subshell once with `) >&2`, which is correct regardless of ordering and cannot regress the same way. Why it went unnoticed: interactively fd1 is the tty, so `2>&1 >&2` is a no-op. It only fires when a caller redirects stdout -- exactly what a CI JSON contract does. Observed impact (2026-09-03): hyperpolymath/session-sentinel#67 reported "Hypatia did not produce a valid JSON findings array" while the scan log read `scan complete: 37 findings >= medium (critical=7, high=2, medium=28); exit 0`. jq failed with "Invalid numeric literal at line 1, column 10" -- the first bytes of `Resolving Hex dependencies...`. Positive control, caller redirection applied OUTSIDE the function: old findings.json = Resolving Hex dependencies...|...|[{...}] jq: FAIL new findings.json = [{"severity":"critical",...}] jq: PASS This is latent in every consumer, not specific to one repo: ~85 estate workflows clone this repo unpinned and invoke the wrapper the same way. They are shielded only while a prebuilt escript is present, so any cache miss or stale-rebuild reintroduces it. `bash -n` clean; `shellcheck -S style` clean. --- hypatia-cli.sh | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/hypatia-cli.sh b/hypatia-cli.sh index 7977e7a9..adb53dbb 100755 --- a/hypatia-cli.sh +++ b/hypatia-cli.sh @@ -37,11 +37,25 @@ BASH_FALLBACK="${HYPATIA_DIR}/hypatia-cli-bash.sh" build_escript() { echo "[hypatia] Building escript..." >&2 + # Everything below is BUILD CHATTER, never scan output. Callers run + # hypatia-cli.sh scan . > hypatia-findings.json + # so any byte written to stdout here lands inside the JSON payload and + # `jq` fails with "Invalid numeric literal" -- reported by the caller as + # "Hypatia did not produce a valid JSON findings array", on a scan that + # actually succeeded. Observed in CI 2026-09-03. + # + # Redirect the whole subshell to stderr ONCE. Do NOT write `2>&1 >&2` + # inside it: redirections are applied left to right, so `2>&1` first + # points fd2 at wherever fd1 currently is -- the caller's findings.json -- + # and the following `>&2` then points fd1 at that same file. Both streams + # end up in the payload, which is the exact opposite of the intent. The + # bug is invisible interactively, where fd1 is the tty and the pair is a + # no-op; it only fires when a caller redirects stdout, i.e. in CI. ( cd "${HYPATIA_DIR}" mix deps.get --quiet 2>/dev/null || true - MIX_NO_PUBSUB="${MIX_NO_PUBSUB:-1}" mix escript.build 2>&1 >&2 - ) + MIX_NO_PUBSUB="${MIX_NO_PUBSUB:-1}" mix escript.build + ) >&2 } # A stale escript is a SOUNDNESS hazard, not a convenience issue: an From b87dbe605e5adf1912c6e2941f66866f0f9b2dc3 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:58:53 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20to=20`fix?= =?UTF-8?q?/build-escript-stdout-pollution`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docstrings generation was requested by @hyperpolymath. * https://github.com/hyperpolymath/hypatia/pull/747#issuecomment-5520323285 The following files were modified: * `hypatia-cli.sh` --- hypatia-cli.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 hypatia-cli.sh diff --git a/hypatia-cli.sh b/hypatia-cli.sh old mode 100755 new mode 100644 index adb53dbb..87e4470b --- a/hypatia-cli.sh +++ b/hypatia-cli.sh @@ -33,7 +33,7 @@ else fi BASH_FALLBACK="${HYPATIA_DIR}/hypatia-cli-bash.sh" -# ─── Build escript if missing ─────────────────────────────────────────── +# build_escript builds the Hypatia escript in the script directory and sends build output to stderr. build_escript() { echo "[hypatia] Building escript..." >&2