ci: tag docs releases automatically on merge to main (#133)

Tags every docs release automatically, so the `docs/vX.Y.Z` tag stops
depending on someone remembering to create it after the merge. So far,
11 of 16 releases were never tagged.

## What changes

- **New workflow, `.github/workflows/tag-docs-release.yml`.** It runs on
every push to `main`. For each new commit titled `Release docs for
paperclip vYYYY.MDD.P`, it tags that commit `docs/vYYYY.MDD.P`.
  - Anything after a space in the title, such as `(#132)`, is ignored.
  - Pre-release versions like `-beta.1` are skipped.
  - Tags that already exist are left alone.
- **`skills/sync-docs/SKILL.md` (Phase 9) and
`maintenance/runbook.md`.** The after-merge step now checks that the tag
exists instead of creating it by hand. It also tells you to keep the
release PR title format, because the workflow reads the version from it.

## Tested

I ran the version matching against all 16 release squash-commit titles
on `main` and it matched every one, including `(content + screenshot
overhaul) (#95)` and titles with no PR number. It rejects `-beta.1`. The
workflow itself hasn't run on GitHub yet; the next release merge will be
its first run.

## Needs

The workflow needs permission to push tags: `permissions: contents:
write`. If the repo's Actions permissions are read-only and that can't
be changed per workflow, the first run will fail with a clear error. Fix
it in Settings → Actions → Workflow permissions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Aron PrinsandClaude Opus 5.5 authored and GitHub committed 2026-09-26 13:37:31 +02:00
1 parent a3368fb06b
commit 478d24ce5d
3 files changed
+64 -7

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
name: Tag docs release
# Tags every release squash commit on `main` as `docs/vYYYY.MDD.P`.
#
# The tag used to be a manual post-merge step, and 11 of the first 16 releases
# never got one. Release PRs are always titled
# `Release docs for paperclip vYYYY.MDD.P`, and a squash merge keeps that title
# as the commit subject, so the version can be read straight from the commit.
on:
push:
branches: [main]
permissions:
contents: write
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Tag release commits
env:
BEFORE: ${{ github.event.before }}
AFTER: ${{ github.sha }}
run: |
set -euo pipefail
# A branch-creating push has an all-zero `before`; look at the tip only.
if [ -z "${BEFORE//0/}" ] || ! git cat-file -e "$BEFORE^{commit}" 2>/dev/null; then
range="$AFTER -1"
else
range="$BEFORE..$AFTER"
fi
git log --format='%H %s' $range | while read -r sha subject; do
version=$(printf '%s\n' "$subject" \
| sed -nE 's/^Release docs for paperclip (v[0-9]{4}\.[0-9]{1,4}\.[0-9]+)( .*)?$/\1/p')
[ -n "$version" ] || continue
tag="docs/$version"
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
echo "$tag already exists at $(git rev-parse --short "$tag^{commit}") — leaving it."
continue
fi
git tag "$tag" "$sha"
git push origin "refs/tags/$tag"
echo "Tagged $tag at ${sha:0:7}."
done
+4 -4
View File
@@ -126,12 +126,12 @@ When a new parent release tag drops (`v2026.X.Y`):
- Frontmatter `paperclip_version` correctly stamped on changed pages.
4. Merge to `main` when satisfied.
5. **After merge — four follow-ups:**
- Tag the merge commit:
- Confirm the merge commit was tagged. The `Tag docs release` workflow does this automatically on the push to `main`, reading the version from the squash commit's subject — so keep the PR title `Release docs for paperclip v2026.X.Y`:
```sh
git checkout main && git pull
git tag docs/v2026.X.Y
git push --tags
git checkout main && git pull --tags
git rev-parse --short docs/v2026.X.Y # should print the squash commit
```
If it's missing, check the workflow run, then tag by hand: `git tag docs/v2026.X.Y <squash-sha> && git push origin docs/v2026.X.Y`.
- Verify `.sync-state.json` on `main` now shows `base_release_tag: v2026.X.Y` and `base_release_sha` matches.
- **Confirm the site actually rebuilt — don't assume.** Cloudflare Pages is supposed to auto-deploy on push to `main`, but that hook has silently no-opped (v2026.824.0 merged with a correct changelog entry and production served the previous build for hours). Check the live site, not the Pages dashboard:
+10 -3
View File
@@ -457,11 +457,18 @@ Skip in nightly mode (Cloudflare branch previews are best-effort, not reader-fac
skipped at least once; none are optional.
```sh
git checkout main && git pull
git tag docs/v2026.X.Y && git push --tags
git checkout main && git pull --tags
git rev-parse --short docs/v2026.X.Y # must print the release squash commit
node scripts/sync/realign-nightly.mjs release/v2026.X.Y --push # see Phase 7
```
The `docs/v2026.X.Y` tag is created by `.github/workflows/tag-docs-release.yml`
on the push to `main`. It reads the version from the squash commit's subject,
so the release PR **must** keep the title `Release docs for paperclip vYYYY.MDD.P`
(anything after a space, like `(#132)`, is fine). If the tag is missing a minute
after the merge, check that workflow's run and tag by hand:
`git tag docs/v2026.X.Y <squash-sha> && git push origin docs/v2026.X.Y`.
Then flip `nightly`'s `.sync-state.json` back to `"branch_mode": "nightly"` and
commit it. The merge-down inherits `"release"` from `main` and nothing else
corrects it, so the next nightly run starts from a state file that lies about
@@ -497,7 +504,7 @@ Skip in nightly mode (Cloudflare branch previews are best-effort, not reader-fac
4. **Show the evidence in the run summary** — the URLs you checked, the status
codes, and the changelog version you read back. "Merged" is not evidence.
> The "never push without asking" rule still holds here. Steps 1 and 3 push tags
> The "never push without asking" rule still holds here. Steps 1 and 3 push the nightly realign
> and publish to production — ask before each, and if the answer is no, hand off
> with the exact commands so the human can run them. What is **not** optional is
> step 2: always check the live site and always report what you found, even when