Skip to content

Harden org CI: gate merges on language CI, bound runtimes, decouple vuln scans#23

Merged
autholykos merged 4 commits into
mainfrom
claude/harden-org-ci
May 29, 2026
Merged

Harden org CI: gate merges on language CI, bound runtimes, decouple vuln scans#23
autholykos merged 4 commits into
mainfrom
claude/harden-org-ci

Conversation

@autholykos

Copy link
Copy Markdown
Contributor

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-main 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.

  • New non-reusable wrappers required-{go,python,rust}-ci.yml (pull_request + merge_group triggers, which GitHub's workflows ruleset rule mandates).
  • apply-org-policy.sh now creates production-{go,python,rust}-ci rulesets, each scoped to tier=production AND language=<lang> (property includes are ANDed → a Python repo is never forced to run Go CI). An upsert_ruleset helper 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 install could burn hours per job across every repo. Added timeout-minutes to 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 own ci.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

  • Composite actions (setup-go, setup-python-uv) collapse the toolchain-setup duplication (Go ×3, Python ×4 → one each). self-ci's zizmor now also scans .github/actions so composite pins stay enforced.
  • merge_group added to the CI templates (future merge-queue safety).
  • claude-reusable got a per-issue/PR concurrency group (no two runners racing the same branch).
  • pytest --cov guarded behind a pytest-cov presence check.

Apply order (important)

The production-<lang>-ci rulesets reference wrappers that must be on main first, 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 own ci.yml if runner minutes matter.

Validation

  • actionlint v1.7.12 (the repo's CI version): clean
  • shellcheck: clean
  • All 19 YAML files parse; generated ruleset payloads are valid JSON

🤖 Generated with Claude Code

autholykos and others added 3 commits May 29, 2026 14:36
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>
Copilot AI review requested due to automatic review settings May 29, 2026 12:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.yml wrappers and corresponding production-<lang>-ci rulesets (created by an upsert_ruleset helper in apply-org-policy.sh) scoped to tier=production AND language=<lang>, with vuln scans turned off in the gated wrappers.
  • New .github/actions/setup-go and .github/actions/setup-python-uv composite actions; go-ci.yml and python-ci.yml refactored to use them and gain timeout-minutes on every job (also added to self-ci, pinact-check).
  • Adds merge_group triggers to workflow templates, a concurrency group to claude-reusable.yml, Dependabot config, zizmor scanning of .github/actions, and production-<lang>-ci documentation in ORG-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>
@autholykos
autholykos merged commit 836074d into main May 29, 2026
5 checks passed
@autholykos
autholykos deleted the claude/harden-org-ci branch May 29, 2026 12:44
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