Skip to content

fix(ci): unmask RE005 exit-masking steps; annotate fail-closed (#942) - #1077

Open
hyperpolymath wants to merge 2 commits into
mainfrom
fix/hypatia-exit-masking
Open

hyperpolymath wants to merge 2 commits into
mainfrom
fix/hypatia-exit-masking

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Closes #942.

Hypatia RE005 flags steps that silently swallow a non-zero exit (|| true, continue-on-error: true). I read every flagged step in context, together with the step that runs after it, and sorted each one into one of three groups:

  • A (real mask): fixed.
  • B (deliberate fail-closed): left as is, with an inline # hypatia: allow research_extensions/RE005 -- <reason> pragma explaining why.
  • Advisory (fails open by design): annotated as such and listed below for the owner.

The per-step reasoning is in the commit message of b2f688c.

Measured locally (hypatia escript from hypatia#883, no Actions minutes)

base bd9313a6 this branch
total findings 150 128
RE005 22 0

Most important fix

security-gate-pr-target.yml "Extract PR branch for safe checkout" had two problems:

  • If the fork's branch could not be fetched, it fell back to a same-named branch in the base repo. A fork PR could therefore have its real content go unscanned while the gate reported success.
  • It silently tolerated fetch and checkout failures.

It now fails closed on both, using ::error:: + exit 1.

Checked for regression risk

governance-reusable.yml runs across the whole estate, so I checked its changes against this risk: dropping || true would break any step running under pipefail whose pipeline ends in grep -v (exit 1 when nothing matches) or find | head (find gets SIGPIPE).

None of the changed steps sets a shell: key, there is no defaults: block, and none sets pipefail in its body. They all run under GitHub's default bash -e {0}, where a pipeline's status is that of its last command (head → 0), so those || true were inert. Where a pipeline ends in grep, the mask is narrowed to || [ $? -eq 1 ] rather than removed. That way a real grep error (exit 2) still fails the step.

Two more checks:

  • actionlint: base and branch report the same 4 pre-existing warnings (SC2317, SC2153, and 2× job.workflow_sha, which this actionlint version doesn't know). No new warnings.
  • The two fail-closed sites in ci-pipeline.yml are annotated in 8e0f96b:
    • The Nickel import scan uses || true only to absorb grep's exit 1 on a file with no imports.
    • The pipeline-ledger checkout's continue-on-error feeds a Verdict step that treats unreadable, empty and blocked the same way.

For the owner: advisory steps that fail open by design

Each of these is annotated rather than changed. Say if you want any of them made blocking.

  1. affinescript-verify.yml "Checkout AffineScript compiler": a best-effort checkout while BLOCKING is false. It surfaces a ::warning::.
  2. governance-reusable.yml "EditorConfig check": a new follow-up step now surfaces a ::warning:: when it fails. Repos opt into blocking locally.
  3. hypatia-scan-reusable.yml "Check out standards for the SARIF baseline filter": the fallback uploads the SARIF unfiltered, which can only show more alerts.
  4. echidna-verify.yml "Type-check proofs": before this change, a real agda --safe failure was unreachable (hidden behind tee's exit status plus || true). It is now detected and reported as a ::warning::. It stays advisory while the proof corpus is evicted (echidna-verify: required context Idris2 — a2ml proofs is vacuously green on every PR and red on every cron #748).

🤖 Generated with Claude Code

https://claude.ai/code/session_01QFphKkDVB9pUDSCD4bkz65

hyperpolymath and others added 2 commits September 30, 2026 11:29
Hypatia RE005 flagged 20 workflow steps across 10 files for silently
swallowing non-zero exit via `|| true` / `continue-on-error: true`. Each
instance was read in context (the step itself plus what runs after it) and
classified as a real mask (A, fixed) or a deliberate fail-closed design
(B, left alone and annotated with an inline `# hypatia: allow` pragma).

1. affinescript-verify.yml "Checkout AffineScript compiler" -- advisory
   annotation added (fails OPEN, not B: best-effort checkout while BLOCKING
   is false; "Verify changed .affine files" surfaces an explicit ::warning::
   instead of a silent green claim).
2. changelog-reusable.yml "Mode = check-only -- verify no drift" -- A:
   removed inert `|| true` (pipe ends in `head -60`; the following `exit 1`
   fails the job either way).
3. ci-pipeline.yml "Checkout the pinned Standards Deno ledger" -- B:
   annotated fail-closed; "Refuse Deno unless this repository is ledgered"
   checks for the ledger file and exits 1 with ::error:: if missing.
4. echidna-verify.yml "Type-check proofs" -- A: rewrote so a real
   `agda --safe` failure is detected (was previously unreachable under
   `tee`'s exit status plus a redundant `|| true`) and surfaced via
   ::warning::, kept advisory since the proof corpus is currently evicted
   (issue #748) and re-entry criteria aren't set.
5-14. governance-reusable.yml (ten instances):
   - "Check banned-language files" -- A: removed inert `|| true` on
     `git ls-files`-terminated captures (RES/GO/SWIFT/DART/VMOD), narrowed
     to `|| [ $? -eq 1 ]` on `grep`-terminated captures (PY/MAKE/JAVA) so a
     real grep error (exit 2) still trips `-e`.
   - "Check for npm/yarn artifacts" -- A: removed 4 inert `|| true`
     (`git ls-files`/`find`-terminated).
   - "Security checks" -- A: removed 3 inert `|| true` (`head`-terminated).
   - "Check file permissions" / "Check TODO/FIXME" / "Check for large
     files" -- A: dropped `continue-on-error: true` entirely (each step
     already always exits 0) and now emit an explicit ::warning:: instead
     of a silent printout.
   - "EditorConfig check" -- advisory annotation added (fails OPEN, not B:
     a follow-up step surfaces ::warning:: on failure; repos opt into
     blocking locally).
   - "Mixed content check" -- A: removed inert `|| true` (`head`-terminated).
   - "Checkout the pinned Standards policy helpers" -- B: annotated
     fail-closed; "Duplicate YAML keys in workflows" refuses to run
     (::error:: + exit 1) when neither script copy is present.
   - "Check locked or SHA-pinned actions" -- A: narrowed `|| true` to
     `|| [ $? -eq 1 ]` (`grep -cve`-terminated; an empty ledger file is an
     expected, not error, case).
15. hypatia-scan-reusable.yml "Check out standards for the SARIF baseline
    filter" -- advisory annotation added (fails OPEN, not B: the fallback
    uploads the SARIF unfiltered, which can only show more alerts).
16. readme-derive-reusable.yml "Freshness check (fail-and-tell)" -- A:
    narrowed `|| true` to `|| [ $? -eq 1 ]` on a bare `diff` display
    command; the following regen instructions + `exit 1` still fire.
17. scorecard-enforcer.yml "Check for pinned dependencies" -- A: removed
    inert `|| true` (`head`-terminated; already emits ::warning:: itself).
18-19. secret-scanner-reusable.yml "Check for hardcoded secrets in Rust" /
   "... in shell scripts" -- A: narrowed `grep`-terminated captures to
   `|| [ $? -eq 1 ]`, removed one inert `sed -n`-terminated `|| true`.
20. security-gate-pr-target.yml "Extract PR branch for safe checkout" -- A:
    the most severe finding. Removed the same-named-base-repo-branch
    fallback (which could let a malicious fork PR's real content go
    unscanned while reporting success) and converted silent tolerance of
    fetch/checkout failure into explicit ::error:: + exit 1.

Validation: `ruby -ryaml -e 'YAML.load_file(...)'` and `yq .` both pass
cleanly on all 10 files. `actionlint` exits 1 but every finding (shellcheck
info/style/warning notes and pre-existing `job.workflow_sha` property
errors) is confirmed pre-existing and unrelated to these edits -- zero new
findings introduced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFphKkDVB9pUDSCD4bkz65
The Nickel import scan's `|| true` absorbs grep's exit 1 on a file
with no imports; the ledger checkout's continue-on-error feeds a
Verdict step that treats unreadable == empty == blocked.

Local scan: RE005 22 -> 0, total findings 150 -> 128.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QFphKkDVB9pUDSCD4bkz65
@coderabbitai

coderabbitai Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 27 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 0518aec0-e3c4-418e-aeee-a594f4d8c6bb

📥 Commits

Reviewing files that changed from the base of the PR and between bd9313a and 8e0f96b.

📒 Files selected for processing (10)
  • .github/workflows/affinescript-verify.yml
  • .github/workflows/changelog-reusable.yml
  • .github/workflows/ci-pipeline.yml
  • .github/workflows/echidna-verify.yml
  • .github/workflows/governance-reusable.yml
  • .github/workflows/hypatia-scan-reusable.yml
  • .github/workflows/readme-derive-reusable.yml
  • .github/workflows/scorecard-enforcer.yml
  • .github/workflows/secret-scanner-reusable.yml
  • .github/workflows/security-gate-pr-target.yml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

This branch has not been deployed

No deployments
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.

Hypatia RE005: review exit-masking steps (|| true / continue-on-error) (20 instances)

1 participant