Commit ed06a78
authored
🤖 ci: gate CI jobs by changed paths (#12)
## Summary
Introduce path-aware CI orchestration so expensive Go jobs only run when
Go-relevant files change.
## Background
Docs-only pull requests were still triggering full Go lint/test/build
workflows. This change preserves CI signal while reducing unnecessary
job execution.
## Implementation
- Added a `changes` job using pinned
`dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36`.
- Added `go`, `workflows`, and `publish` outputs from that job.
- Gated `lint` and `test` on `needs.changes.outputs.go == 'true'`.
- Gated `lint-actions` on `needs.changes.outputs.workflows == 'true'`.
- Updated `publish-main` to depend on `changes` and to allow upstream
`success` or `skipped` outcomes while still requiring `publish ==
'true'`.
## Validation
- `make verify-vendor`
- `make test`
- `make build`
- `go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.10`
## Risks
Low. Scope is limited to workflow orchestration. Main risk is incorrect
path classification causing a job to skip unexpectedly; mitigated by
explicit filters and preserving required job dependency/result checks
for publish.
---
<details>
<summary>📋 Implementation Plan</summary>
# Plan: Option 2 — Per-job path filtering in CI
## Context / Why
The current `.github/workflows/ci.yaml` runs Go-heavy jobs (`lint`,
`test`) for every pull request, including docs-only changes (for
example, markdown edits under `.mux/` and `AGENTS.md`). The goal is to
keep CI signal quality while avoiding unnecessary Go
setup/lint/test/build work when no Go-relevant files changed.
## Evidence
- `.github/workflows/ci.yaml` currently has no `paths`/`paths-ignore`
filters at trigger or job level.
- `lint` and `test` always run on `pull_request` and `push` to `main`.
- `publish-main` currently depends on `[test, lint, lint-actions]`, so
any job-level skipping logic must avoid accidentally blocking publish
when dependencies are skipped by design.
These sources are sufficient to plan the workflow-only change because
the optimization is isolated to CI orchestration in
`.github/workflows/ci.yaml`.
## Implementation details
1. **Add a `changes` classifier job in `.github/workflows/ci.yaml`**
- Insert a first job that computes file-change categories with
`dorny/paths-filter` (SHA-pinned).
- Emit outputs for at least:
- `go` (Go source/build/vendor/tooling inputs)
- `workflows` (`.github/workflows/**`)
- `publish` (files that should trigger `publish-main`, usually superset
of `go` plus container build files)
```yaml
jobs:
changes:
runs-on: ubuntu-latest
outputs:
go: ${{ steps.filter.outputs.go }}
workflows: ${{ steps.filter.outputs.workflows }}
publish: ${{ steps.filter.outputs.publish }}
steps:
- name: Checkout
uses: actions/checkout@34e1148 # v4.3.1
with:
persist-credentials: false
- name: Detect changed paths
id: filter
uses: dorny/paths-filter@<PINNED_SHA> # v3
with:
filters: |
go:
- '**/*.go'
- 'go.mod'
- 'go.sum'
- 'vendor/**'
- 'Makefile'
workflows:
- '.github/workflows/**'
publish:
- '**/*.go'
- 'go.mod'
- 'go.sum'
- 'vendor/**'
- 'Dockerfile*'
- 'Dockerfile.goreleaser'
```
2. **Gate expensive jobs using `needs: changes` + `if:` guards**
- `lint` and `test` should run only when `needs.changes.outputs.go ==
'true'`.
- `lint-actions` should run only when `needs.changes.outputs.workflows
== 'true'`.
- Keep `codex-comments` behavior unchanged (PR-only, cheap,
policy-oriented).
```yaml
lint:
needs: changes
if: needs.changes.outputs.go == 'true'
...
test:
needs: changes
if: needs.changes.outputs.go == 'true'
...
lint-actions:
needs: changes
if: needs.changes.outputs.workflows == 'true'
...
```
3. **Update `publish-main` so intentionally skipped dependencies do not
block it**
- Keep existing `push`/`main` gating.
- Add `needs: [changes, test, lint, lint-actions]`.
- Use an `if:` expression with `always()` and explicit dependency result
checks (`success` or `skipped`) plus `publish == 'true'`.
```yaml
publish-main:
needs: [changes, test, lint, lint-actions]
if: |
always() &&
github.event_name == 'push' &&
github.ref == 'refs/heads/main' &&
needs.changes.outputs.publish == 'true' &&
(needs.test.result == 'success' || needs.test.result == 'skipped') &&
(needs.lint.result == 'success' || needs.lint.result == 'skipped') &&
(needs.lint-actions.result == 'success' || needs.lint-actions.result ==
'skipped')
...
```
4. **Validate behavior with targeted scenarios**
- Run `go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.10`
locally after YAML edits.
- Open/refresh a PR with markdown-only changes; confirm `lint`/`test`
are skipped and required checks remain mergeable.
- Open/refresh a PR with Go file changes; confirm `lint`/`test` execute.
- Confirm push to `main` with docs-only diff does not publish image
(`publish == false`), while code-related push still publishes.
<details>
<summary>Why this structure (brief)</summary>
- Job-level filtering (instead of top-level `paths-ignore`) preserves
visibility of all jobs/check names while avoiding unnecessary work.
- A dedicated `changes` job centralizes path logic so future categories
can be added in one place.
- Explicit `success || skipped` checks avoid accidental publish blockage
when upstream jobs are intentionally skipped.
</details>
</details>
---
_Generated with [`mux`](https://github.com/coder/mux) • Model:
`openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$0.16`_
<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh
costs=0.16 -->1 parent 8ae6328 commit ed06a78
1 file changed
Lines changed: 54 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
13 | 52 | | |
| 53 | + | |
| 54 | + | |
14 | 55 | | |
15 | 56 | | |
16 | 57 | | |
| |||
46 | 87 | | |
47 | 88 | | |
48 | 89 | | |
| 90 | + | |
| 91 | + | |
49 | 92 | | |
50 | 93 | | |
51 | 94 | | |
| |||
77 | 120 | | |
78 | 121 | | |
79 | 122 | | |
| 123 | + | |
| 124 | + | |
80 | 125 | | |
81 | 126 | | |
82 | 127 | | |
| |||
122 | 167 | | |
123 | 168 | | |
124 | 169 | | |
125 | | - | |
126 | | - | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
127 | 179 | | |
128 | 180 | | |
129 | 181 | | |
| |||
0 commit comments