Skip to content

fix(sweep): stop repo_settings.yml aborting silently under bash -e - #294

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/repo-settings-bash-e-abort
Aug 26, 2026
Merged

Jammy2211 merged 1 commit into
mainfrom
feature/repo-settings-bash-e-abort

Conversation

@Jammy2211

Copy link
Copy Markdown
Contributor

Closes #293. Fixes a regression in #292, merged an hour earlier.

What broke

GitHub runs a run: block as /usr/bin/bash -e {0}. repo_settings.yml set -uo pipefail and never disabled -e, so the per-repo read

before=$(gh api "repos/$slug" --jq '.delete_branch_on_merge' 2>/dev/null)

aborted the whole script the moment one repo was unreadable — silently, because that stderr is discarded. The case "$before" in ... *) unreadable branch, and with it the entire in-org (error) vs out-of-org (warning) failure-policy split #292 was partly written to add, was dead code in production.

The first real dispatch (run 32992618192, mode=audit) failed in 20s with no stdout at all between ##[endgroup] and ##[error]Process completed with exit code 1. That silence is the signature.

A second instance was latent: [ "$hard" -eq 1 ] && failed=1 ends visit() in two branches, and with hard=0 the compound returns 1, so the function returned 1 and -e aborted on the first out-of-org repo. Confirmed independently — pre-fix, the out-of-org PATCH-refused test exits 1 after printing its warning.

The PATCH path itself was always fine: if gh api ...; then is -e-safe.

Why it shipped — the part worth keeping

#292 was control-tested, across seven scenarios, before merge. The harness ran bash <script>. GitHub runs bash -e <script>. The harness did not reproduce the caller's invocation, so it validated a shell mode that never executes, and seven green scenarios said nothing about the one that mattered.

So the tests are the real fix here, not the two-line shell change. tests/test_repo_settings_workflow.py parses the workflow, extracts the block, stubs gh on PATH, and runs it as the runner does — and pins that with test_the_step_does_not_override_the_shell, so if anyone adds a shell: key the assumption fails loudly instead of rotting.

Verified non-vacuous: checked out the pre-fix workflow and ran the new tests against it — 3 failed, 3 passed; restored, 6 pass. A regression test that passes on the broken code would have been worse than none.

The fix

  • set +e immediately after set -uo pipefail, commented with why. Explicit rather than || true on the read, because the hazard applies to every guarded command in the block, not just that one.
  • Both trailing [ ... ] && failed=1 compounds become if blocks.

Sibling sweep

Audited every run: block in the repo. branch_archive.yml and branch_sweep_all.yml explicitly set -e and discard no stderr — abort-on-error is intended there, so they are consistent, not defective, and are left alone. branch_sweep.yml, brain_board.yml, docs.yml, nightly-release.yml show no matching pattern. repo_settings.yml was the only block combining an inherited -e with a swallowed failure.

Verification

  • pytest tests/ -q → 554 passed (548 before, +6 new).
  • New tests fail against the pre-fix workflow, pass against this one (above).
  • Tenant firewall OK; fixtures use invented repo names, as this is a .py under a framework organ.
  • check_skill_line_counts.sh 47/47; YAML parses.

Still open after this

We do not yet know which repo failed to read. The aborted run proves at least one did, and the silence is exactly what hid it. The acceptance test for this PR is a fresh audit dispatch that goes green and names any repo whose read fails — a green run naming nothing is only acceptable if all 46 reads genuinely succeed.

Heart remains RED on release validation FAILED (integrate:fail, since 2026-08-23), unrelated to this diff; shipped under the same standing human override as #292.


Generated by Claude Code

GitHub runs a `run:` block as `bash -e {0}`. The block set `-uo pipefail` and
never disabled `-e`, so the per-repo read

    before=$(gh api "repos/$slug" --jq '.delete_branch_on_merge' 2>/dev/null)

aborted the entire script the moment one repo was unreadable — and silently,
because that stderr is discarded. The `case "$before" in ... *)` unreadable
branch, and with it the in-org/out-of-org failure-policy split #292 added, could
never execute. The first real dispatch failed in 20s with no output at all.

A second instance was latent: `[ "$hard" -eq 1 ] && failed=1` ends `visit()` in
two branches, and with hard=0 that compound returns 1, so the function returned
1 and `-e` aborted on the first out-of-org repo. Both are now `if` blocks.

`-e` is disabled explicitly rather than with `|| true` on the read, because the
hazard applies to every guarded command in the block.

The tests are the real fix. #292 was control-tested across seven scenarios and
still shipped this, because the harness ran `bash <script>` while GitHub runs
`bash -e <script>` — it validated a shell mode that never executes. The new
tests run the extracted block exactly as the runner does, and were verified to
FAIL against the pre-fix workflow and pass against this one.

Sibling sweep: branch_archive.yml and branch_sweep_all.yml explicitly `set -e`
and discard no stderr, so abort-on-error is intended there; no other run: block
in this repo combines a swallowed failure with an inherited -e.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqUT9Wc6mVLiJmbcstT7Z3
@Jammy2211
Jammy2211 merged commit 8b48a93 into main Aug 26, 2026
2 checks passed
@Jammy2211
Jammy2211 deleted the feature/repo-settings-bash-e-abort branch August 26, 2026 17:26
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.

fix(sweep): repo_settings.yml aborts silently under GitHub's bash -e

1 participant