Files
ersatztv/docs/decisions/records/ci/docs-only-skip-steps.md
T
5fb9c8537a
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 10s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 17s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 9m20s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m25s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 5m48s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m10s
docs(747): re-verify the Gitea 1.25.4-pinned CI claims on 1.27.1, and measure the merge-gate semantics that were source-attested (#867)
Population derived from `git ls-files`, not the issue's 9-key list (~21 claim sites).

Re-confirmed unchanged on 1.27.1: the distinct `skipped` commit-status state; `compare` serving
no `files`; no agent-side cancel route (REST route + swagger only); `branches: [main]` suppressing
the run off a non-main base.

Newly measured on four throwaway scratch bases, `main`'s rule never PATCHed: an absent required
context blocks an ORDINARY merge without needing `block_admin_merge_override` (that field governs
the FORCE path only), and `enable_bypass_allowlist` with an empty list is NOT a substitute for it.
Trap recorded: the PR API reports `mergeable: true` while such a merge is refused.

Left explicitly dated with reasons: push-supersession auto-cancel, `pull_request_target` overlap,
`--depth=1` no-merge-base, and the scope-enum/`reqRepoWriter`/403 items. Not a corpus sweep, and
`ci.actions-credential-scoping` now says so. `review-verdict.yml` untouched — #763 holds that file.

Five adversarial review rounds (21/12/9/6/2). Caveat: all same-model-family; Codex was rate-limited.

fixes #747

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
2026-08-28 16:51:13 +00:00

3.7 KiB

key, title, status, since, supersedes, superseded-by, rule, signals, mechanics
key title status since supersedes superseded-by rule signals mechanics
ci.docs-only-skip-steps 2026-07-17 — Docs-only CI skip gates STEPS in always-running required jobs, never `if:`-skips them (#416) active 2026-07-17 none none A docs-only change must still run every required job (`test`, `migrations`) so their commit-status contexts always report; each heavy job runs `scripts/ci-detect-docs-only.sh` first and gates its real STEPS on `if: steps.detect.outputs.docs_only != 'true'`, never `if:`-skips the whole job (an `if:`-skipped job reports `skipped`, not `success`, which branch protection may never unblock on). Detection biases toward running more on any doubt. docs-only CI skip, required-context branch protection, step-level gating vs job-level `if:` · paths: `.gitea/workflows/docker-build.yml`, `scripts/ci-detect-docs-only.sh` · issues: #416, #418, #420, #398 `docs/ci-cd.md` → "Docs-only skip"

A change touching only docs/** or *.md ran the entire docker-build.yml matrix (test, migrations incl. its mysql:8.4 service, functional-e2e, format, api-docs) — ~9 min of warm CI to validate Markdown. docker-build.yml had no path filtering.

Why not paths-ignore / an if:-skipped job — the trap. main's branch protection requires two contexts by name (Build & test (.NET), EF migration integrity (SQLite + MySql)). If a docs-only PR produced no run for them, those contexts never report and the PR can never merge — the naive fix bricks docs PRs instead of speeding them. A probe (throwaway PR #418) confirmed that on Gitea 1.25.4 an if:-skipped job reports commit-status state skipped (a distinct state, not success); how branch protection treats a skipped required context is not something we rely on. Re-confirmed unchanged on 1.27.1, 2026-08-28 (#747), without a new probe PR — Build & push image (amd64) is if:-skipped on every PR, and reports skipped — a state distinct from success — on the heads of PR #829 (e5a01dc84100) and PR #828 (02bb0393). The distinct state survived the upgrade, so the reason this decision gates STEPS rather than jobs still holds.

The decision. Each heavy job (test, migrations, functional-e2e, build) runs scripts/ci-detect-docs-only.sh as its first post-checkout step (id: detect) and gates every real step on if: steps.detect.outputs.docs_only != 'true'. The job always runs and reports success in seconds on docs-only — so the two required contexts report unconditionally (safe by construction). Non-required jobs may skip freely (production proves a skipped non-required context doesn't block merge — build is skipped on every PR), so build skips its image steps on a docs-only push to main (docs aren't in the image); tag builds force docs_only=false so a release is never skipped. api-docs/format already self-short-circuit; docs-reminder/ decisions-guard/ci-image-pin keep running.

Detection biases toward running MORE. docs_only=true only when every changed path is docs; any code path, a tag build, a non-merge push, or an undeterminable diff → false (run everything). A false true would skip real tests on a code change (a correctness bug); a false false merely wastes CI. Trade-off accepted: migrations' mysql service still starts on a docs-only run (a services: container starts with the job regardless of step if:), but the 787-migration replay — the expensive part — is skipped.

Two adjacent redundancies are deliberately out of scope: the whole matrix re-running on a PR and again on the merge-to-main over identical code (#420), and the within-run triple dotnet build (#398). Full mechanism in ci-cd.md → "Docs-only skip".