Harden org CI: gate merges on language CI, bound runtimes, decouple vuln scans#23
Merged
Conversation
Add `timeout-minutes` to every job in the reusable Go/Python/Rust CI plus pinact-check and self-ci. Default GHA job timeout is 360 min, long enough for a hung test or stalled `go install`/`cargo install` to burn hours of runner time across every nantobv repo. Ceilings are generous, not targets. Collapse the duplicated toolchain setup into two composite actions: - setup-go: the dual go-version / go-version-file block (was repeated 3x) - setup-python-uv: install uv -> install python -> uv sync (was repeated 4x) Reusables reference them as nantobv/.github/.github/actions/*@main (auto- fetched in the caller's context; covered by the existing .pinact.yaml exception, with zizmor-ignore on the unpinned-uses). self-ci's zizmor now also scans .github/actions so the SHA pins inside composites stay enforced. Also guard python-ci's pytest --cov behind a pytest-cov presence check, so a repo missing that dev-dep runs tests instead of failing before any run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The production-main ruleset only required that a PR exist (0 approvals,
no required status checks), so a red build could self-merge — CI was
advisory, not a gate.
Add non-reusable wrappers required-{go,python,rust}-ci.yml (pull_request +
merge_group triggers, which the GitHub `workflows` ruleset rule mandates)
and generalize apply-org-policy.sh to create per-language rulesets
production-{go,python,rust}-ci. Each is scoped to tier=production AND
language=<lang> (repository_property includes are ANDed), so a Python repo
is never forced to run Go CI. An upsert_ruleset helper de-dupes the
create-vs-update logic; APPLY_REQUIRED_CI=false skips the block while the
wrappers aren't yet on main.
The wrappers call the reusables with run-vuln-scan: false (Rust:
run-cargo-audit: false), keeping the merge gate correctness-only. A freshly
disclosed CVE in a transitive dep therefore can't turn every production
repo's merges red overnight; vuln scanning stays advisory in each repo's
own ci.yml.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add .github/dependabot.yml so this repo keeps its own pinned action SHAs (and the new composite actions) current — it enforces pinning org-wide but had no mechanism to refresh its own pins. - Add merge_group triggers to the go/python/rust CI templates so enabling a merge queue later doesn't let code merge without validating the merge result. - Give claude-reusable a per-issue/PR concurrency group (cancel-in-progress: false) so two rapid @claude mentions on one thread don't race two runners pushing the same branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Hardens org-wide CI by gating production merges on language-specific required CI workflows, adding job-level timeout-minutes ceilings, decoupling vulnerability scanning from the merge gate, and reducing duplication via new composite actions for Go and Python+uv setup. Also adds Dependabot for this repo, a per-issue/PR concurrency group on claude-reusable, merge_group triggers on workflow templates, and guards pytest --cov behind a pytest-cov presence check.
Changes:
- New
required-{go,python,rust}-ci.ymlwrappers and correspondingproduction-<lang>-cirulesets (created by anupsert_rulesethelper inapply-org-policy.sh) scoped totier=production AND language=<lang>, with vuln scans turned off in the gated wrappers. - New
.github/actions/setup-goand.github/actions/setup-python-uvcomposite actions;go-ci.ymlandpython-ci.ymlrefactored to use them and gaintimeout-minuteson every job (also added toself-ci,pinact-check). - Adds
merge_grouptriggers to workflow templates, aconcurrencygroup toclaude-reusable.yml, Dependabot config, zizmor scanning of.github/actions, andproduction-<lang>-cidocumentation inORG-POLICY.md.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| workflow-templates/{go,python,rust}-ci.yml | Add merge_group trigger to org-default CI templates |
| scripts/apply-org-policy.sh | New upsert_ruleset helper; creates production-<lang>-ci rulesets gated by tier+language properties |
| ORG-POLICY.md | Documents new per-language CI rulesets, bootstrap flag, and split of pinact-check wrapper |
| .github/workflows/self-ci.yml | Adds timeout-minutes and extends zizmor scan to .github/actions |
| .github/workflows/rust-ci.yml | Adds timeout-minutes to all jobs |
| .github/workflows/required-rust-ci.yml | New non-reusable wrapper for the required Rust CI gate (cargo-audit off) |
| .github/workflows/required-python-ci.yml | New non-reusable wrapper for the required Python CI gate (vuln scan off) |
| .github/workflows/required-go-ci.yml | New non-reusable wrapper for the required Go CI gate (vuln scan off) |
| .github/workflows/python-ci.yml | Replace duplicated uv/Python/sync steps with composite; add timeouts; guard --cov on pytest-cov presence |
| .github/workflows/pinact-check.yml | Add timeout-minutes |
| .github/workflows/go-ci.yml | Replace dual setup-go blocks with composite action; add timeouts |
| .github/workflows/claude-reusable.yml | Add per-issue/PR concurrency group (no cancel-in-progress) |
| .github/dependabot.yml | Weekly grouped Dependabot updates for this repo's own action SHAs |
| .github/actions/setup-python-uv/action.yml | New composite: install uv, Python, and sync deps (optionally locked) |
| .github/actions/setup-go/action.yml | New composite: Go toolchain with go.sum-keyed cache, honoring go-version-file |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The required-{go,python,rust}-ci.yml wrappers carry a pull_request trigger
(mandatory for a ruleset-required workflow), which also fires them on
nantobv/.github's own PRs — where there is no go.mod/pyproject.toml/
Cargo.toml, so they failed instantly. Guard each job with
`if: github.repository != 'nantobv/.github'`: it's true (runs) when injected
into any other repo, false (skips, still succeeds) on the home repo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Hardens the org-wide CI control plane for robustness — closing gaps where the pipeline could fail open, hang, or rot. Came out of a thorough review of the whole pipeline.
What & why
P1 — Gate production merges on CI
production-mainonly required that a PR exist (0 approvals, no required status checks), so a red build could self-merge — CI was advisory, not a gate.required-{go,python,rust}-ci.yml(pull_request+merge_grouptriggers, which GitHub'sworkflowsruleset rule mandates).apply-org-policy.shnow createsproduction-{go,python,rust}-cirulesets, each scoped totier=productionANDlanguage=<lang>(property includes are ANDed → a Python repo is never forced to run Go CI). Anupsert_rulesethelper de-dupes create-vs-update.P1 — Bound every CI job runtime
Default GHA job timeout is 360 min. A hung test or stalled
go install/cargo installcould burn hours per job across every repo. Addedtimeout-minutesto every job in go/python/rust CI + pinact-check + self-ci.P2 — Decouple vuln scans from the merge gate
The required wrappers call reusables with
run-vuln-scan: false(Rust:run-cargo-audit: false). A newly-disclosed CVE in a transitive dep can no longer turn every production repo's merges red overnight. Vuln scanning stays advisory in each repo's ownci.yml.P2 — Dogfood Dependabot
Added
.github/dependabot.yml— the repo enforces SHA-pinning org-wide but had no way to refresh its own pins (incl. the new composite actions).P3 — Smaller robustness items
setup-go,setup-python-uv) collapse the toolchain-setup duplication (Go ×3, Python ×4 → one each).self-ci's zizmor now also scans.github/actionsso composite pins stay enforced.merge_groupadded to the CI templates (future merge-queue safety).claude-reusablegot a per-issue/PRconcurrencygroup (no two runners racing the same branch).pytest --covguarded behind apytest-covpresence check.Apply order (important)
The
production-<lang>-cirulesets reference wrappers that must be onmainfirst, or the GitHub API returns 422. Merge this PR first, then run./scripts/apply-org-policy.sh. While bootstrapping:APPLY_REQUIRED_CI=false ./scripts/apply-org-policy.sh.Known tradeoff
A production repo runs lint+test twice — advisory (
ci.yml, vuln on) + the lean required gate (vuln off). Deliberate: advisory vuln scan + a hard gate a CVE can't wedge. A repo can slim its ownci.ymlif runner minutes matter.Validation
actionlintv1.7.12 (the repo's CI version): cleanshellcheck: clean🤖 Generated with Claude Code