📝 Add docstrings to fix/build-escript-stdout-pollution - #749
Merged
Conversation
`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.
Docstrings generation was requested by @hyperpolymath. * #747 (comment) The following files were modified: * `hypatia-cli.sh`
Contributor
Author
|
Important Review skippedThis PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Essentials Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
hyperpolymath
approved these changes
Sep 3, 2026
hyperpolymath
added a commit
that referenced
this pull request
Sep 3, 2026
PR #749 (`coderabbitai[bot]`, "Add docstrings") rewrote `hypatia-cli.sh` through a GitHub write API that carries **path and contents but no file mode**, so the blob landed at `100644`. The demotion is invisible in the diff — it lives in the tree object, not the patch text. ## Timeline | commit | time | mode | |---|---|---| | `8f0d1aec` (#747, the stdout fix) | 10:59:56Z | `100755` ✅ | | `12473951` (#749, docstrings) | 11:01:01Z | `100644` ❌ | **65 seconds.** ## Effect on consumers Every consumer that invokes the wrapper directly now dies before it can do anything: ``` /home/runner/hypatia/hypatia-cli.sh: Permission denied ##[error]Hypatia scanner execution failed with exit 126 ``` `Run Hypatia scan = failure` → `Emit check annotations`, `Upload hypatia findings` and the verdict step are all **skipped**, and the downstream `Deposit findings for gitbot-fleet` job then fails on `Artifact not found for name: hypatia-findings`. Observed live on `hyperpolymath/scaffoldia` run [`33749291439`](https://github.com/hyperpolymath/scaffoldia/actions/runs/33749291439) (started 11:22:03Z) and on four more repos. The split is **temporal, not invocation-shape**: every consumer run after 11:01:01Z fails this way. The two repos that still scanned cleanly — `hybrid-automation-router` run `33714565319` (04:18:31Z) and `session-sentinel` run `33714770073` (04:21:59Z) — are *pre-demotion witnesses*, and would fail with 126 if re-run today. ## Scope, measured not assumed Comparing the **full recursive trees** of `8f0d1aec` and `12473951` — 935 blobs each — yields exactly **one** mode change, this file. Every other `.sh` in the repo root is `100755`; this was the only `100644` among them, and the file carries `#!/usr/bin/env bash`. ## What this diff does Restores the mode. **Nothing else** — the blob SHA is `87e4470b` before and after, so the docstrings from #749 are kept byte-for-byte. A mode change cannot be expressed through GraphQL `createCommitOnBranch`, which is the API shape that caused this, so this is a real signed git commit (`%G?` = `G`). Consumers clone `main` unpinned, so merging cures the fleet on the next run with no per-repo change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Docstrings generation was requested by @hyperpolymath.
The following files were modified:
hypatia-cli.shℹ️ Note