Skip the test gate on docs-only branches #34

Merged
lilleman merged 3 commits from ci-docs-only-skip into main 2026-08-02 14:08:55 +02:00
Owner

ci.yml skips bash ci.sh when every path a branch changes against its merge-base with main ends in .md.

The job still runs and still builds + pushes the commit-hash image — CI / full-gate (push) stays green for the merge gate, and release.yml still finds an image for every main commit. Anything else in the diff, an empty diff, or an unreadable range falls through to the full gate.

`ci.yml` skips `bash ci.sh` when every path a branch changes against its merge-base with `main` ends in `.md`. The job still runs and still builds + pushes the commit-hash image — `CI / full-gate (push)` stays green for the merge gate, and `release.yml` still finds an image for every `main` commit. Anything else in the diff, an empty diff, or an unreadable range falls through to the full gate.
lilleman added 1 commit 2026-08-02 13:31:09 +02:00
Skip the test gate on docs-only branches
CI / full-gate (push) Successful in 2m41s
6c850b8923
lilleman reviewed 2026-08-02 13:32:35 +02:00
@@ -12,0 +12,4 @@
fetch-depth: 0 # the merge-base below needs history; checkout defaults to depth 1
- name: Detect a docs-only branch
id: scope
run: |
Author
Owner

There is a bunch of scripts here in "run". Could this be written in the ci.sh script instead, to make this all testable locally?

There is a bunch of scripts here in "run". Could this be written in the ci.sh script instead, to make this all testable locally?
lilleman marked this conversation as resolved
lilleman added 1 commit 2026-08-02 13:38:32 +02:00
Author
Owner

Agreed — moved in 175717f. The whole decision is now a docs_only() function at the top of ci.sh, and the workflow is back to a plain - run: bash ci.sh (it keeps only fetch-depth: 0, since a shallow clone has no merge-base to compare against).

So bash ci.sh on your branch now tells you exactly what CI will do. Two things that fell out of moving it:

  • It also counts the working tree, not just committed changes — running the gate locally on a docs-only branch with uncommitted code would otherwise have skipped over that code.
  • Everything it can't determine (no git, unreachable main, no merge-base) falls through to the full gate, never to a skip.

Verified against a throwaway repo with its own origin — real bash ci.sh on a docs-only branch exits 0 without touching docker, and the function across the rest:

scenario
committed *.md only skip
+ uncommitted *.md skip
+ uncommitted or untracked .ts gate
committed code change gate
no diff vs main gate
*.md renamed to *.ts gate
unreachable origin gate

One thing I could not do: unit-test the git plumbing in the normal suite — the test container is node:24-alpine and has no git, and adding it just for that felt like the wrong trade. src/ci-gate.test.ts covers what is checkable from the files: the decision lives in ci.sh and not the workflow, checkout stays unshallow, and the image push is never conditioned (release.yml needs an image for every main commit).

Agreed — moved in 175717f. The whole decision is now a `docs_only()` function at the top of `ci.sh`, and the workflow is back to a plain `- run: bash ci.sh` (it keeps only `fetch-depth: 0`, since a shallow clone has no merge-base to compare against). So `bash ci.sh` on your branch now tells you exactly what CI will do. Two things that fell out of moving it: - It also counts the **working tree**, not just committed changes — running the gate locally on a docs-only branch with uncommitted code would otherwise have skipped over that code. - Everything it can't determine (no git, unreachable `main`, no merge-base) falls through to the full gate, never to a skip. Verified against a throwaway repo with its own origin — real `bash ci.sh` on a docs-only branch exits 0 without touching docker, and the function across the rest: | scenario | | | --- | --- | | committed `*.md` only | skip | | + uncommitted `*.md` | skip | | + uncommitted or untracked `.ts` | gate | | committed code change | gate | | no diff vs main | gate | | `*.md` renamed to `*.ts` | gate | | unreachable origin | gate | One thing I could not do: unit-test the git plumbing in the normal suite — the test container is `node:24-alpine` and has no `git`, and adding it just for that felt like the wrong trade. `src/ci-gate.test.ts` covers what is checkable from the files: the decision lives in `ci.sh` and not the workflow, checkout stays unshallow, and the image push is never conditioned (release.yml needs an image for every `main` commit).
lilleman reviewed 2026-08-02 13:49:25 +02:00
@@ -11,1 +11,4 @@
with:
fetch-depth: 0 # ci.sh's docs-only check needs history; checkout defaults to depth 1
- run: bash ci.sh
# Runs even when ci.sh no-ops on a docs-only branch: release.yml re-tags this exact image, and
Author
Owner

Remove this comment, it is the inner workings of ci.sh and should be obvious if you read the shell scripts code. It is also not very important to know when you are day to day developing.

Remove this comment, it is the inner workings of ci.sh and should be obvious if you read the shell scripts code. It is also not very important to know when you are day to day developing.
lilleman marked this conversation as resolved
lilleman reviewed 2026-08-02 13:50:48 +02:00
README.md Outdated
@@ -1166,6 +1166,12 @@ Each E2E suite **owns a clean stack** — never point two suites at one backend
revokes the admin's sessions; full-flow writes users/groups/roles to Keto), which is why the
gate runs them serially, one stack up/down per suite.
It **no-ops on a docs-only change** — when every path that differs from `main` (committed *and*
Author
Owner

Does this really help anyone? If someone really wants to know why some runs went faster than others, then can quickliy find out why by looking at the logs or reading the ci.sh script. This big blob of text in the README only makes it cluttered and harder to consume for both humans and machines. Please fix this and also adjust the projects AGENTS.md to not do this kind of documentation in these places again.

Does this really help anyone? If someone really wants to know why some runs went faster than others, then can quickliy find out why by looking at the logs or reading the ci.sh script. This big blob of text in the README only makes it cluttered and harder to consume for both humans and machines. Please fix this and also adjust the projects AGENTS.md to not do this kind of documentation in these places again.
lilleman marked this conversation as resolved
lilleman reviewed 2026-08-02 13:51:35 +02:00
README.md Outdated
@@ -1188,6 +1194,12 @@ no repo files involved): direct pushes are blocked, changes land via PR only, th
**fast-forward-only** — history stays linear and `main`'s head is the exact commit hash of
the merged branch, which is why the branch's push-triggered status carries over.
**Docs-only branches skip the gate** — [`ci.sh` no-ops](#the-full-gate-one-command) when a branch
Author
Owner

More of the same as the previous comment. Look through the README to see if there are more stuff like this we can remove that is not relevant for the day to day developer and can be easily found out if you dig into the code.

More of the same as the previous comment. Look through the README to see if there are more stuff like this we can remove that is not relevant for the day to day developer and can be easily found out if you dig into the code.
lilleman marked this conversation as resolved
lilleman added 1 commit 2026-08-02 13:55:50 +02:00
Trim README and workflow prose that restates the code
CI / full-gate (push) Successful in 2m33s
Mirror / github-mirror (push) Successful in 6s
6db0f57bf4
Author
Owner

Removed in 6db0f57.

The one comment I kept in ci.yml is fetch-depth: 0's — that one is not about ci.sh, it records that actions/checkout defaults to depth 1, so dropping the line silently breaks the check with no error. Say the word if you want it gone too.

The invariant the removed comment was carrying (image push must never be conditioned) is asserted in src/ci-gate.test.ts, which is where it actually holds.

Removed in 6db0f57. The one comment I kept in `ci.yml` is `fetch-depth: 0`'s — that one is not about ci.sh, it records that `actions/checkout` defaults to depth 1, so dropping the line silently breaks the check with no error. Say the word if you want it gone too. The invariant the removed comment was carrying (image push must never be conditioned) is asserted in `src/ci-gate.test.ts`, which is where it actually holds.
Author
Owner

You're right — both blobs are gone in 6db0f57, along with the clause I had added to the ci.sh line in the file map. The only trace left in the README is six words in the existing workflow table (bash ci.sh, a no-op on a docs-only branch), since that table's job is to say what each workflow does. Happy to drop that too if you'd rather have nothing.

AGENTS.md gained a rule under README structure: don't document internals there — how a script reaches a decision, why one run behaved differently, what a function guards. It belongs in the code it describes, or nowhere. The README earns its length on what you can't dig out: usage, operation, external contracts, one-time setup.

On the wider sweep you asked for — I read the whole thing. Two more places recited an algorithm that already lives in a linked, unit-tested script, so I compressed them in the same commit:

  • Registry retention (CI/CD) — five lines spelling out which tags survive and that untagged sha256:… child manifests go too. Now: registry-cleanup/cleanup.ts defines what survives. The operational warning next to it (don't add a pattern-based org cleanup rule) stays — that one is a Gitea setting you can't learn from the code.
  • Auto-release levels (CI/CD) — the major→minor, minor/patch→patch pre-1.0 mapping, restated from next-version.ts. Now just: it never auto-crosses into 1.0.0, which stays a hand-cut tag. The Release-Bump trailer and the renovate-bot-PAT-or-it-won't-fire constraint stay — both are external facts.

Net for the README across this PR: -33 lines, +10.

One judgment call I did not make on my own: the per-suite prose in End-to-end (Playwright) (~70 lines) is the same pattern — it narrates what each spec asserts and why the full-flow suite needs a same-origin proxy, all readable in the specs. But unlike a script's internals, a short "what does this suite cover" line genuinely helps you pick which suite to run. I'd cut each suite down to one sentence plus its run command, roughly halving the section. Want that, here or as its own PR?

I left the rest alone deliberately: Architecture (the system's shape, not internals), the Ory/cookie and canonical-host explanations (real footguns you'd hit blind), and every one-time-setup block (secrets, accounts, tokens — nowhere in the code).

You're right — both blobs are gone in 6db0f57, along with the clause I had added to the `ci.sh` line in the file map. The only trace left in the README is six words in the existing workflow table (`bash ci.sh`, a no-op on a docs-only branch), since that table's job is to say what each workflow does. Happy to drop that too if you'd rather have nothing. `AGENTS.md` gained a rule under **README structure**: don't document internals there — how a script reaches a decision, why one run behaved differently, what a function guards. It belongs in the code it describes, or nowhere. The README earns its length on what you can't dig out: usage, operation, external contracts, one-time setup. **On the wider sweep you asked for** — I read the whole thing. Two more places recited an algorithm that already lives in a linked, unit-tested script, so I compressed them in the same commit: - **Registry retention** (CI/CD) — five lines spelling out which tags survive and that untagged `sha256:…` child manifests go too. Now: `registry-cleanup/cleanup.ts` defines what survives. The operational warning next to it (don't add a pattern-based org cleanup rule) stays — that one is a Gitea setting you can't learn from the code. - **Auto-release levels** (CI/CD) — the `major`→minor, `minor`/`patch`→patch pre-1.0 mapping, restated from `next-version.ts`. Now just: it never auto-crosses into `1.0.0`, which stays a hand-cut tag. The `Release-Bump` trailer and the renovate-bot-PAT-or-it-won't-fire constraint stay — both are external facts. Net for the README across this PR: **-33 lines, +10**. **One judgment call I did not make on my own:** the per-suite prose in [End-to-end (Playwright)](#end-to-end-playwright) (~70 lines) is the same pattern — it narrates what each spec asserts and why the full-flow suite needs a same-origin proxy, all readable in the specs. But unlike a script's internals, a short "what does this suite cover" line genuinely helps you pick which suite to run. I'd cut each suite down to one sentence plus its run command, roughly halving the section. Want that, here or as its own PR? I left the rest alone deliberately: Architecture (the system's shape, not internals), the Ory/cookie and canonical-host explanations (real footguns you'd hit blind), and every one-time-setup block (secrets, accounts, tokens — nowhere in the code).
lilleman merged commit 6db0f57bf4 into main 2026-08-02 14:08:55 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: larvit/plainpages#34