Skip to content

changelog: roll the unreleased entries up as v0.7.0 (#1754) #104

changelog: roll the unreleased entries up as v0.7.0 (#1754)

changelog: roll the unreleased entries up as v0.7.0 (#1754) #104

Workflow file for this run

name: Full check
# THE HEAVY GATE SITS ON BOTH PROMOTION POINTERS. Work reaches `dev` fast and
# lightly checked, which is the right trade while people are building; the
# price is paid before a commit reaches `staging` or the release branch `main`.
#
# It also runs nightly against `dev`, because the failure this repository
# actually suffers is not "the pull request was bad" but "the merge of two good
# pull requests was bad", and nobody is watching at the moment of the merge.
# A nightly run names the day something started being broken.
#
# docs/rules/ci.md says which of these are required to merge and what has to be
# true before the rest join them.
on:
pull_request:
branches:
- staging
- main
push:
branches:
- staging
- main
schedule:
# 09:00 UTC, which is the small hours in California and the morning in India
# — a result waiting on both desks rather than a run nobody sees finish.
- cron: "0 9 * * *"
workflow_call:
inputs:
ref:
required: true
type: string
workflow_dispatch:
inputs:
ref:
description: "commit to check; empty = the branch you dispatch on"
required: false
type: string
permissions:
contents: read
concurrency:
group: full-check-${{ inputs.ref || github.ref }}
cancel-in-progress: false
jobs:
# THE WHOLE SUITE, MINUS WHAT IS ALREADY OWED. .github/known-red.txt lists the
# tests that fail on a clean tree; they are skipped here so that red in this
# job means this change caused it. The ledger is the debt and shrinking it is
# the work — see the file's own header.
#
# IN THREE SHARDS, BECAUSE THE MACHINE IS SMALL AND THE BINARIES ARE NOT. A
# free-plan private repository gets a two-core, seven-gigabyte runner, and this
# repository's test binaries are heavy — internal/tui3's links most of the
# product. The suite's first run died of exactly that: every package result in
# by seven minutes except the eight link-heaviest, then thirteen silent
# minutes, then the runner itself was shut down under it. Test time was never
# the problem — 483 seconds across 82 packages — the peak was. Three machines
# holding a third of the load each stay comfortably inside their memory, and
# the suite's wall clock becomes compile-plus-a-few-minutes.
#
# The split is round-robin over `go list` order, computed at run time. Not a
# hand-kept list of packages per shard: a list would go stale with the next
# package added, and stale here means silently untested.
# THE JOB'S CEILING SITS ABOVE THE GO ONE. Each package gets `make test`'s
# fifteen minutes so that a hang panics with the package's name; a job
# GitHub kills first names nothing. A shard is about thirty packages run
# one at a time, most in seconds, and the whole of it has come in under
# ten minutes; sixty leaves a hung package its full fifteen and the rest
# of the shard theirs.
full-tests-shard:
name: full tests ${{ matrix.shard }}/3
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
shard: [0, 1, 2]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
# A short TMPDIR, because at least one test builds a unix socket under it
# and the default runner path is long enough to overflow the 104-byte
# sun_path limit. Cheaper to give it room than to skip the test.
#
# THE SKIP LIST AND THE TIMEOUT ARE THE MAKEFILE'S, through `make test`,
# so this run and a laptop's `make check` are one proof. The old eight
# minutes here was a guess, and internal/tui3 takes about 485 seconds on
# this runner: the 2026-09-02 nightly cut it off at the finish line and
# reported a test that had been running for two seconds as a hang (#372).
# The Makefile's TEST_TIMEOUT is measured with headroom instead.
- name: Test
env:
TMPDIR: /tmp/codeaf-ci
SHARD: ${{ matrix.shard }}
run: |
set -euo pipefail
mkdir -p "$TMPDIR"
pkgs="$(go list ./... | awk -v s="$SHARD" 'NR % 3 == s' | paste -sd' ' -)"
echo "shard ${SHARD}: $(echo "$pkgs" | wc -w) packages"
# -p 1: ONE PACKAGE AT A TIME WITHIN A SHARD. The second full run died
# the same way as the first — a runner shutdown with slow packages in
# flight and no Go timeout — after sharding had already cut each
# machine's load to a third. Halving the peak again by serialising is
# the experiment that settles whether the killer is memory: if a shard
# still dies at -p 1, it is not, and the package that was running is
# the suspect by name. The shards keep the wall clock acceptable
# while each one runs serial.
make test TEST_FLAGS='-p 1' PKGS="$pkgs"
# ONE NAME FOR THE THREE, for the same reason cross build has one below: a
# branch rule can only require a name it can spell, and a matrix's legs are
# not spellable in advance.
full-tests:
name: full tests
needs: [full-tests-shard]
if: always()
runs-on: ubuntu-latest
steps:
- name: Every shard passed
run: |
set -euo pipefail
result='${{ needs.full-tests-shard.result }}'
echo "full tests shards: ${result}"
[ "$result" = "success" ]
# EVERY PLATFORM THE PRODUCT SHIPS ON, COMPILED. This is the one gate here
# whose answer is identical on every machine and every run, so it is the one
# that blocks a promotion. A build tag or a syscall that only exists on Linux
# is invisible on the laptop that wrote it and obvious here.
cross-build-matrix:
# One check per target, so a red one names the platform it broke on.
name: cross build (${{ matrix.goos }}/${{ matrix.goarch }})
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- { goos: darwin, goarch: amd64 }
- { goos: darwin, goarch: arm64 }
- { goos: linux, goarch: amd64 }
- { goos: linux, goarch: arm64 }
- { goos: windows, goarch: amd64 }
- { goos: windows, goarch: arm64 }
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Build
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: go build ./...
# ONE NAME FOR THE SIX. A matrix publishes its checks as `cross build (linux,
# arm64)` and so on, and a branch rule can only require a name it can spell —
# adding a target would silently leave the new one unrequired. This job is the
# spellable name: it is green only when every leg of the matrix was.
cross-build:
name: cross build
needs: [cross-build-matrix]
if: always()
runs-on: ubuntu-latest
steps:
- name: Every target built
run: |
set -euo pipefail
result='${{ needs.cross-build-matrix.result }}'
echo "cross build matrix: ${result}"
[ "$result" = "success" ]
# TWO MACHINES, ACTUALLY TWO — three containers sharing no path, no home and
# no credential, so a session that only works because both halves happen to be
# one filesystem fails here. The Makefile calls this a nightly or pre-release
# gate rather than an every-change one, and this is that nightly.
remote:
name: remote
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Two machines
run: make test-remote
# A RED NIGHTLY IS A PAGE, NOT A LOG LINE. Every nightly this repository had
# run before 2026-09-02 was red, and nobody knew, because a scheduled run
# reports to nobody — there is no pull request to decorate and no push to
# notify. So a red night opens one issue, or adds the night's run to the one
# already open, and the issue is what somebody sees in the morning. Green
# nights say nothing; the issue is closed by the person who fixed it.
page:
name: page on a red nightly
needs: [full-tests, cross-build, remote]
# A cancelled job — the runner shutdown this file describes above — is
# not a failure to GitHub, and it is exactly the night to be told about.
if: always() && !inputs.ref && github.event_name == 'schedule' && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled'))
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Open or update the issue
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
SHA: ${{ github.sha }}
RESULTS: "full tests: ${{ needs.full-tests.result }}, cross build: ${{ needs.cross-build.result }}, remote: ${{ needs.remote.result }}"
run: |
set -euo pipefail
title='ci: the nightly full check is red on dev'
body="$(printf 'The nightly full check failed on `%s`.\n\n%s\n\nRun: %s\n\nThis issue is opened by ci-full.yml and closed by whoever makes the nightly green. docs/rules/ci.md says what runs there.' "${SHA:0:8}" "$RESULTS" "$RUN_URL")"
number="$(gh issue list --state open --search "\"$title\" in:title" --json number,title --jq "map(select(.title == \"$title\")) | .[0].number // empty")"
if [ -n "$number" ]; then
gh issue comment "$number" --body "$body"
else
gh issue create --title "$title" --label bug --body "$body"
fi
# THE BINARY'S WEIGHT, REPORTED AND NOT ENFORCED — YET. SIZE-BUDGET is set on
# darwin/amd64, the heaviest shipped platform (PERF.md, the size ratchet), and
# this runner is linux/amd64, about a megabyte lighter, so a pass here does not
# prove the cap holds where it is measured. It prints both so the drift is
# visible; making it block means weighing darwin/amd64 here as well.
size:
name: size (informational)
runs-on: ubuntu-latest
timeout-minutes: 15
continue-on-error: true
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Weigh the binary
run: |
set -euo pipefail
make build
actual="$(stat -c%s bin/codeaf)"
budget="$(cat SIZE-BUDGET)"
printf 'bin/codeaf on linux/amd64: %s bytes. SIZE-BUDGET (set on darwin/amd64): %s.\n' \
"$actual" "$budget"