ci: no image build on push — split cheap checks from builds
Lint / hadolint (push) Successful in 8s
Lint / docs-check (push) Successful in 14s
Lint / actionlint (push) Successful in 6m41s

A push to main used to trigger validate.yml, which builds an amd64 variant and
runs the smoke test. Pushing work-in-progress to main therefore cost a build.
Restructure so the three workflows divide by cost, matching pi-devbox:

  lint.yml                 cheap checks, every push/PR — the ONLY workflow a
                           push to main triggers
  validate.yml             amd64 build + smoke test — pull_request and
                           workflow_dispatch only, no push trigger at all
  docker-publish-split.yml the release path, tag-only (unchanged)

docs-check (DOCKER_HUB.md vs HUB_TEMPLATE) moves from validate.yml to lint.yml.
It needs no image, and anything that needs no image belongs in the workflow that
actually runs on push — otherwise the doc-drift guard would have been silently
lost when validate.yml stopped running on pushes. That guard earns its keep: it
has caught real drift.

validate-base/validate-omos additionally keep a `github.event_name != 'push'`
clause. It is redundant now that the trigger is gone, and deliberately so:
re-adding a push trigger later cannot silently re-enable builds on every push.

Renamed lint.yml `Lint workflows` -> `Lint`, since it now covers Dockerfiles and
docs as well as workflows. No references to the old name existed.

Safe because the release path already fails closed: docker-publish-split.yml
pushes variant tags only after smoke-base/smoke-omos pass and promotes
base-latest last, so an aborted release leaves at worst an unreferenced
base-<hash> blob on Hub — never a half-published version tag. Pre-tag
validation remains available three ways: open a PR, dispatch Validate, or
dispatch docker-publish-split.yml against a throwaway tag with
promote_latest=false — the only route that also exercises a CHANGED base, which
validate.yml structurally cannot (it builds variants on Hub's base-latest).

Coverage lost is narrower than it looks: validate-base/validate-omos were
already skipped whenever a commit touched Dockerfile.base, rootfs/, or
entrypoint*.sh, so they only ever ran for variant-only changes — most usefully a
bare OPENCODE_VERSION bump, for which an explicit dispatch is now the
equivalent.

Verified with the CI-pinned actionlint 1.7.7 and scripts/check-workflow-shell.sh.
AGENTS.md updated in the same commit (file roles for both workflows + the
trigger-model convention). The CHANGELOG entry lands with the v2.9.0 changeset
in the following commit, which is one unreleased block covering all of v2.9.0.
This commit is contained in:
pi
2026-08-04 16:23:03 +02:00
parent d2f2396c4a
commit 5fb07e0a39
3 changed files with 70 additions and 42 deletions
+33 -9
View File
@@ -1,13 +1,20 @@
name: Lint workflows
name: Lint
# Durable guard against CI-workflow bugs — most importantly the "bash-only
# syntax under the default `sh`/dash shell" footgun. Ported from pi-devbox,
# where this class broke resolve-versions (ed49b8d) and promote-base-latest
# (b7197e8 → run 418). actionlint runs shellcheck against each `run:` step
# using its *effective* shell, so `set -o pipefail` under dash is flagged as
# SC3040 before any expensive build runs. This is cheap (~10s) and independent
# of the build pipeline, so it fires on every push/PR — not just on release
# tags, which is where docker-publish-split.yml is otherwise only triggered.
# The repo's cheap-checks workflow: everything that can be verified WITHOUT
# building an image. Runs on every push and PR, in ~30s, independent of the
# build pipeline. Three jobs: workflow lint (actionlint + the Gitea shell
# guard), Dockerfile lint (hadolint), and the DOCKER_HUB.md docs-sync check.
#
# Deliberately the ONLY workflow that runs on a push to main. validate.yml
# (amd64 build + smoke) is PR/dispatch-only and docker-publish-split.yml is
# tag-only, so pushing work-in-progress to main never starts an image build.
#
# Its original purpose, still the most important one: the "bash-only syntax
# under the default `sh`/dash shell" footgun. Ported from pi-devbox, where this
# class broke resolve-versions (ed49b8d) and promote-base-latest (b7197e8 → run
# 418). actionlint runs shellcheck against each `run:` step using its
# *effective* shell, so `set -o pipefail` under dash is flagged as SC3040
# before any expensive build runs.
on:
push:
pull_request:
@@ -90,3 +97,20 @@ jobs:
- name: Run hadolint
run: hadolint Dockerfile.base Dockerfile.variant
docs-check:
# Fails if DOCKER_HUB.md is out of sync with what generate-dockerhub-md.py
# would produce from HUB_TEMPLATE. Keeps the two docs from drifting.
#
# Lives here rather than in validate.yml because it needs no image: keeping
# it in the cheap workflow means it still runs on every push to main now
# that validate.yml is PR/dispatch-only. Reproduce locally with
# `python3 scripts/generate-dockerhub-md.py --check`.
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
steps:
- uses: actions/checkout@v4
- name: Check DOCKER_HUB.md is in sync with HUB_TEMPLATE
run: python3 scripts/generate-dockerhub-md.py --check