Repository navigation
fix(ci): unblock unrelated PRs from the bash32 required-check deadlock - #513
Merged
Merged
Conversation
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>
mogul
approved these changes
Sep 24, 2026
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>
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.
What
Fixes a required-status-check deadlock in this repo's CI:
.github/workflows/bash32-compat.yml's job (the one backing the required checkacq offline suite under bash 3.2) had a workflow-levelpaths: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 sitsmergeStateStatus: BLOCKEDeven 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:
changes— cheap job, computes relevance viagit diff --name-onlyagainst the PR base. Same technique this repo's ownmarkdown-quality.ymllink-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."test-acq-bash32— the real, expensive job (builds bash 3.2.57 from source, runs the offline suite). Now conditional onchangesreporting relevant. Renamed (was the required-check name; see below) to avoid two jobs racing to post the same status-check context.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:
Without that
changes-result check, a broken diff-computation step would cause the expensive job to be silently skipped (because itsif: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-agentsconsensus panel before implementing (architect/security/devex/pm/catfish approved; scope_steward rejected on cost-justification grounds, arguing simply dropping thepaths: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 bareneeds-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 (checkingchanges'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:
actionlintclean on the whole workflow.git diff --name-onlyrelevance logic tested against two real commits in a scratch clone (workflow-only change → empty diff →relevant=false;acq.backends/msb.shchange → non-empty diff →relevant=true).(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.