Skip to content

fix(ci): unblock unrelated PRs from the bash32 required-check deadlock - #513

Merged
mogul merged 1 commit into
mainfrom
ci/bash32-gate-path-filter-deadlock
Sep 24, 2026
Merged

mogul merged 1 commit into
mainfrom
ci/bash32-gate-path-filter-deadlock

Conversation

@wz-gsa

@wz-gsa wz-gsa commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

What

Fixes a required-status-check deadlock in this repo's CI: .github/workflows/bash32-compat.yml's job (the one backing the required check acq offline suite under bash 3.2) had a workflow-level paths: filter. When a PR's diff doesn't touch those paths, GitHub creates no check-run at all for that context — not a passing "skipped" status, nothing. Branch protection then waits forever for a context that structurally can never post, and the PR sits mergeStateStatus: BLOCKED even though every check that actually ran is green.

This is a real, reproduced problem, not theoretical: it blocked #449 (a routine dependabot actions-version bump touching only .github/workflows/release.yml) and required an admin-bypass merge to get past.

Why this shape

This is GitHub's own documented failure mode ("Handling skipped but required checks" in their required-status-checks troubleshooting docs), and the fix is their documented mitigation — a 3-job "gate" pattern:

  1. changes — cheap job, computes relevance via git diff --name-only against the PR base. Same technique this repo's own markdown-quality.yml link-check job already uses, so no new marketplace action. Fails closed: any error computing the diff is a job failure, never a silent "not relevant."
  2. test-acq-bash32 — the real, expensive job (builds bash 3.2.57 from source, runs the offline suite). Now conditional on changes reporting relevant. Renamed (was the required-check name; see below) to avoid two jobs racing to post the same status-check context.
  3. gate — always-runs (if: always()), and is what branch protection actually requires — under the same required-check name the single job used before (acq offline suite under bash 3.2). Keeping the name unchanged means this PR needs no out-of-band branch-protection/ruleset update — a subtlety a couple of the review panel's voters specifically flagged as worth optimizing for, since a ruleset edit is live GitHub state, not reviewable in a PR diff.

The gate is fail-closed on both upstream jobs, not just the expensive one — this was the single most-repeated correctness note across the review panel:

if [ "$CHANGES_RESULT" != "success" ]; then
  exit 1   # a failure in relevance-detection must not be laundered into a pass
fi
case "$TEST_RESULT" in
  success|skipped) : ;;   # skipped is the correct, expected outcome for irrelevant diffs
  *) exit 1 ;;             # failure/cancelled always fails the gate
esac

Without that changes-result check, a broken diff-computation step would cause the expensive job to be silently skipped (because its if: condition can't evaluate true), and a naive gate would report that skip as green — quietly disabling the bash 3.2 regression-catcher while showing a passing required check. That's strictly worse than today's loud deadlock.

Process

Ran this through a 7-role nexus-agents consensus panel before implementing (architect/security/devex/pm/catfish approved; scope_steward rejected on cost-justification grounds, arguing simply dropping the paths: filter — with no gate job — would already unblock merges today, and challenging the "prevents future path-filtered required checks" framing as speculative). I adopted the gate-job design anyway because a bare needs-dependent job with no gate can still launder a failed dependency into a passing required check (GitHub's own docs flag this as "may not block merging" — not a hard guarantee either way), and this repo already has exactly one other required-conditional-check candidate shape worth getting right once. I did fold in the panel's fail-closed correctness fix (checking changes's own result, not just the expensive job's) rather than shipping the simpler two-state version most voters initially reviewed.

Independently reproduced/verified before pushing, not just read from docs:

  • actionlint clean on the whole workflow.
  • git diff --name-only relevance logic tested against two real commits in a scratch clone (workflow-only change → empty diff → relevant=false; acq.backends/msb.sh change → non-empty diff → relevant=true).
  • Gate shell logic exercised directly against all 5 (changes result, test result) combinations — passes only on (success, success) and (success, skipped), fails on every other combination including the failure-laundering case (failure, skipped).

Docs

Added Known Failure Mode #41 documenting the deadlock, root cause, and the prevention rule ("any workflow backing a required check must have no trigger-level path filter, or must follow this gate-job pattern") so a future required-check addition doesn't reintroduce the same class of bug.

AI-assisted (OpenCode); disclosed per this repo's AGENTS.md PR-level attribution policy.

The branch-protection required check "acq offline suite under bash 3.2"
came from a job in bash32-compat.yml whose workflow-level pull_request
trigger had a paths: filter (acq, acq.backends/**, scripts/test-acq-lib.sh,
scripts/test-acq-bats, test/bats/**). When a PR's diff didn't touch those
paths, GitHub never created a check-run for that job at all -- not a
'skipped, passing' status, no status whatsoever -- so the required context
sat 'Expected'/pending forever and blocked merge even though every check
that DID run was green. This is GitHub's own documented failure mode for a
required check backed by a path-filtered workflow trigger.

It just blocked #449 (a routine dependabot
actions-version bump touching only .github/workflows/release.yml) and
required an admin-bypass merge to unblock.

Fix (the documented gate-job mitigation, validated via a 7-role
nexus-agents consensus_vote panel, 83% approve):

- Removed the workflow-level paths: filter so the workflow always triggers.
- Added a cheap 'changes' job that computes relevance via git diff
  --name-only against the PR base (same technique markdown-quality.yml's
  link-check job already uses -- no new marketplace action). Fails closed:
  any error computing the diff is a job failure, never a silent
  'not relevant'.
- The expensive job (build bash 3.2.57 from source, run the offline suite)
  is now conditional on the changes job reporting relevant; renamed to
  avoid a duplicate-job-name status-check ambiguity with the gate below.
- Added an always-running 'gate' job that IS the required check, posted
  under the SAME required-check name the single job used before
  ('acq offline suite under bash 3.2') -- so this fix needs zero
  out-of-band branch-protection/ruleset changes. It fails closed on BOTH
  upstream jobs: needs.changes.result must be success (a failure in the
  cheap relevance step must not be laundered into a pass via the
  downstream job's consequent skip), and needs.test-acq-bash32.result must
  be success or skipped.

Verified locally: reproduced the changes job's git-diff logic against two
real commits (workflow-only change -> relevant=false; acq.backends/msb.sh
change -> relevant=true), and exercised the gate's shell logic directly
against all 5 (changes result, test result) combinations -- passes only on
(success, success) and (success, skipped), fails on every other
combination including the failure-laundering case (failure, skipped).
actionlint clean on all workflow files.

Documented as Known Failure Mode #41 (prevention: any workflow backing a
required check must have no trigger-level path filter, or must follow this
gate-job pattern).

Co-authored-by: OpenCode Agent <william.zujkowski@gsa.gov>
@wz-gsa
wz-gsa requested a review from a team as a code owner September 24, 2026 16:01
@mogul
mogul merged commit 187aa57 into main Sep 24, 2026
7 checks passed
@mogul
mogul deleted the ci/bash32-gate-path-filter-deadlock branch September 24, 2026 19:54
mogul added a commit that referenced this pull request Oct 6, 2026
🤖 I have created a release *beep* *boop*
---


##
[4.0.0](v3.1.0...v4.0.0)
(2026-10-06)


### ⚠ BREAKING CHANGES

* **kits:** acq no longer installs podman/OCI tooling by default. Users who need docker, docker compose, or rootless podman when the base image doesn't provide it must opt into the oci-engine kit with `acq configure`, `--kit`, or `ACQ_EXTRA_KITS`.
* **kits:** acq no longer performs native OpenCode npm fallback installation. OpenCode is supplied by the pinned opencode agent kit from agentic-coding-patterns v1.12.0, currently delivering OpenCode v2.0.22. If the kit is unavailable or invalid, acq fails closed instead of installing opencode-ai from npm.
* **kits:** existing acq sandboxes with adapter-installed podman are not migrated in place. Recreate affected sandboxes or add the oci-engine kit before relying on OCI behavior after stop/start.

### Features

* **acq:** choose the host port per sandbox, and let --publish pin it
([#526](#526))
([410f332](410f332))
* **acq:** export ACQ_WORKSPACE and ACQ_CLONE into the guest on both
backends
([#457](#457))
([e0b9a3b](e0b9a3b))
* **acq:** interactive `acq configure` UI for extra kits + token-scoping
defaults
([#500](#500))
([13d9d3a](13d9d3a))
* **kits:** adopt pinned agent and capability kits
([#474](#474))
([6a67063](6a67063))
* **secrets:** add Windows-native DPAPI secret backend
([#464](#464))
([907f404](907f404))
* **windows:** add acq preview install path
([#463](#463))
([d6d76f4](d6d76f4))


### Bug Fixes

* **acq:** use sandbox workspace for github scoping
([#476](#476))
([1610835](1610835))
* **ci:** bump js-yaml to 4.3.2 to clear a high-severity CPU-DoS
advisory
([#454](#454))
([3217b49](3217b49))
* **ci:** bump smol-toml to 1.7.1 to clear an infinite-loop DoS advisory
([#459](#459))
([c2291bf](c2291bf))
* **ci:** centralize markdownlint coverage
([#548](#548))
([a27c31e](a27c31e))
* **ci:** pin markdownlint-cli2 once, clear GHSA-253c, unify file
selection
([#527](#527))
([e712685](e712685))
* **ci:** unblock unrelated PRs from the bash32 required-check deadlock
([#513](#513))
([187aa57](187aa57))
* **install:** keep curl | sh stdin intact and tap msb formula first
([#462](#462))
([c808d74](c808d74))
* **install:** trust the microsandbox-acq dependency before brew install
([#534](#534))
([ffac0fa](ffac0fa))
* **kits:** land collapsed kit stack implementation
([#549](#549))
([bcd5b9c](bcd5b9c))
* **msb:** allow Playwright browser downloads
([#477](#477))
([0c399c5](0c399c5))
* **msb:** block unsafe 0.7 migration versions
([#510](#510))
([3634046](3634046))
* **msb:** carry every origin URL, keep the scratch private, fail closed
on a copy miss
([#488](#488))
([babcd49](babcd49))
* **msb:** carry the source checkout's git identity into the --clone
scratch
([#443](#443))
([320d784](320d784))
* **msb:** follow the host resolvers and disable DNS rebind protection
in the guest
([#444](#444))
([d2f4bdc](d2f4bdc))
* **msb:** point the --clone scratch's origin at the source checkout's
remote
([#455](#455))
([7f48b94](7f48b94))
* **msb:** refresh ssh authorization for port publish
([#446](#446))
([72a6bd5](72a6bd5))
* **msb:** session parity for exec/attach/shell (cwd, terminal identity,
login shell)
([#427](#427))
([6261eda](6261eda))
* **msb:** skip the recursive home chown when the image baked the agent
user
([#432](#432))
([d8f66a4](d8f66a4))
* **msb:** thread the merged kit env onto kit lifecycle commands
([#519](#519))
([2248a92](2248a92))
* **msb:** translate kit wildcard network rules
([#451](#451))
([e22bbc3](e22bbc3))
* **msb:** verify post-hoc port forward listener
([#530](#530))
([8ca50e1](8ca50e1))
* **msb:** warn on broadened kit wildcard rules
([#460](#460))
([6ded3ce](6ded3ce))
* **msb:** warn when ssh-agent route is missing
([#448](#448))
([3a767a4](3a767a4))
* **sbx:** emit valid setup.install shape for the git-identity mixin
([#492](#492))
([8e6160c](8e6160c))
* **sbx:** raise the version floor to 0.39.0, where sbx create --env
first exists
([#491](#491))
([30f9ecf](30f9ecf))
* **sbx:** wait for startup barrier before attach
([#512](#512))
([33c83c5](33c83c5))
* **secret:** reject multiline test secret values
([#447](#447))
([f2da950](f2da950))
* **secrets:** propagate rotation across backends
([#509](#509))
([9641958](9641958))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Bret Mogilefsky <bret.mogilefsky@gsa.gov>
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.

2 participants