Skip to content

📝 Add docstrings to fix/build-escript-stdout-pollution - #749

Merged
hyperpolymath merged 3 commits into
mainfrom
coderabbitai/docstrings/b74e052
Sep 3, 2026
Merged

📝 Add docstrings to fix/build-escript-stdout-pollution#749
hyperpolymath merged 3 commits into
mainfrom
coderabbitai/docstrings/b74e052

Conversation

@coderabbitai

@coderabbitai coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Docstrings generation was requested by @hyperpolymath.

The following files were modified:

  • hypatia-cli.sh
ℹ️ Note

CodeRabbit cannot perform edits on its own pull requests yet.

hyperpolymath and others added 2 commits September 3, 2026 05:23
`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`
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Important

Review skipped

This 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 configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 19c3fdb3-c05e-4e3b-a10e-75c112925d92

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

Base automatically changed from fix/build-escript-stdout-pollution to main September 3, 2026 10:59
@hyperpolymath
hyperpolymath merged commit 1247395 into main Sep 3, 2026
49 of 56 checks passed
@hyperpolymath
hyperpolymath deleted the coderabbitai/docstrings/b74e052 branch September 3, 2026 11:01
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.
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.

1 participant