From a0e398c8fc333320d68f6b251f36bf3f913e4d8e Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 20:24:39 +0000 Subject: [PATCH 1/2] ci: run the full candidate matrix on release tags and diff-plan main pushes Main pushes now plan from the pushed diff like pull requests, so untouched surfaces skip their jobs. Release tags trigger a full CI run whose artifacts the release workflow consumes; the tag workflow pins to that run and waits for it. The image job derives the release version from the previous tag when building a tag, since the tag itself has no commits after it. --- .github/workflows/ci.yml | 47 +++++++++++++++-------------------- .github/workflows/release.yml | 29 ++++++++++++++------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a45f613e..3faf033f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ name: CI on: push: branches: [main] + tags: ["v*"] pull_request: branches: [main] @@ -55,8 +56,13 @@ jobs: run: | if [[ "$EVENT_NAME" == pull_request ]]; then bun scripts/ci-plan.mjs --base "$BASE_SHA" --head "$GITHUB_SHA" + elif [[ "$GITHUB_REF" == refs/tags/* || "$BEFORE_SHA" =~ ^0+$ || -z "$BEFORE_SHA" ]]; then + # Release tags and the first push have no usable diff base, and the + # release pipeline consumes their candidate artifacts. + bun scripts/ci-plan.mjs --full else - bun scripts/ci-plan.mjs --full --base "$BEFORE_SHA" --head "$GITHUB_SHA" + # Main pushes plan from the pushed diff so untouched surfaces skip. + bun scripts/ci-plan.mjs --base "$BEFORE_SHA" --head "$GITHUB_SHA" fi policy: @@ -196,35 +202,13 @@ jobs: bun-version: ${{ env.BUN_VERSION }} - env: OPENPOST_GO_COVERAGE_FILE: coverage.out + OPENPOST_GO_TEST_RACE: "1" run: bun run test -- backend - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: backend/coverage.out fail_ci_if_error: false - backend-race: - name: Backend race tests - needs: plan - if: needs.plan.outputs.backend == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - sparse-checkout: | - /* - !/frontend/static/image-editor-models/ - sparse-checkout-cone-mode: false - - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version-file: backend/go.mod - cache-dependency-path: backend/go.sum - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 - with: - bun-version: ${{ env.BUN_VERSION }} - - env: - OPENPOST_GO_TEST_RACE: "1" - run: bun run test -- backend - hosted-data-plane: name: Hosted PostgreSQL and object storage needs: plan @@ -794,8 +778,18 @@ jobs: id: manifest shell: bash run: | - latest_tag="$(git for-each-ref --count=1 --sort=-version:refname --format='%(refname:short)' 'refs/tags/v*')" - [[ -n "$latest_tag" ]] + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + # The newest tag is the one being built, so version resolution must + # range from the previous release tag; there are no commits after it. + latest_tag="$(git describe --match 'v*' --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || true)" + [[ -n "$latest_tag" ]] || { + echo "No previous release tag found before ${GITHUB_REF_NAME}." >&2 + exit 1 + } + else + latest_tag="$(git for-each-ref --count=1 --sort=-version:refname --format='%(refname:short)' 'refs/tags/v*')" + [[ -n "$latest_tag" ]] + fi bun run check -- provider-certification bun scripts/release-manifest.mjs create --changelog CHANGELOG.md --latest-tag "$latest_tag" --revision "$GITHUB_SHA" --output release-manifest.json version="$(jq -er .version release-manifest.json)" @@ -891,7 +885,6 @@ jobs: backend-lint, frontend-quality, backend, - backend-race, hosted-data-plane, frontend-build, marketing-build, diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1ecda8f0..be77c2b5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,19 +54,30 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash run: | - candidate="$(gh run list \ - --repo "$GITHUB_REPOSITORY" \ - --workflow CI \ - --commit "$GITHUB_SHA" \ - --event push \ - --limit 20 \ - --json databaseId,conclusion \ - --jq '[.[] | select(.conclusion == "success")][0].databaseId // empty')" + # The tag-triggered CI run starts alongside this release and builds + # every candidate artifact. Wait for it, then require success. + candidate="" + for _ in $(seq 1 30); do + candidate="$(gh run list \ + --repo "$GITHUB_REPOSITORY" \ + --workflow CI \ + --commit "$GITHUB_SHA" \ + --event push \ + --limit 20 \ + --json databaseId,headBranch \ + --jq '[.[] | select(.headBranch == "'"$GITHUB_REF_NAME"'")][0].databaseId // empty' 2>/dev/null || true)" + [[ -n "$candidate" ]] && break + sleep 20 + done [[ -n "$candidate" ]] || { - echo "No successful CI run exists for tagged SHA $GITHUB_SHA." >&2 + echo "No CI run exists for tag $GITHUB_REF_NAME on SHA $GITHUB_SHA." >&2 exit 1 } [[ "$candidate" =~ ^[0-9]+$ ]] + gh run watch "$candidate" \ + --repo "$GITHUB_REPOSITORY" \ + --exit-status \ + --interval 30 > /dev/null echo "run_id=$candidate" >> "$GITHUB_OUTPUT" - name: Resolve artifacts from their successful CI attempts id: artifacts From 5c60b6a6a8e90f4ca41a34db39059c1644dc4a95 Mon Sep 17 00:00:00 2001 From: "usehoplite[bot]" <288093033+usehoplite[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 20:24:39 +0000 Subject: [PATCH 2/2] ci: run backend tests once with race and coverage The race suite duplicated the backend suite on every backend change. One -race -cover run keeps both signals at the cost of the slower run alone. --- changes/ci-surface-planning.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changes/ci-surface-planning.md diff --git a/changes/ci-surface-planning.md b/changes/ci-surface-planning.md new file mode 100644 index 00000000..633da720 --- /dev/null +++ b/changes/ci-surface-planning.md @@ -0,0 +1,8 @@ +### Changed + +- CI now runs the full release-candidate matrix for release tags instead of + every push to `main`. Main pushes plan from the pushed diff, so untouched + surfaces (for example the Android candidate) skip their jobs, and the release + workflow waits for the tag's own CI run before promoting its artifacts. +- Backend tests run once with the race detector and coverage together instead + of as two separate suites.