From adaad181241136f8ee4ce1752aef1776ea98f383 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 29 Aug 2026 04:41:43 +0000 Subject: [PATCH] fix(ci): ci-image stops rebuilding the identical image every ship MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - package.json out of the trigger paths: the tag hash deliberately excludes it (version bumps every ship), so every merge rebuilt and re-pushed the IDENTICAL tag (~2m26s for zero content change); patches/** added (it IS a tag input) - manifest existence check (mirrors evals.yml): tag already exists → skip the build - concurrency group: two rapid main pushes raced pushing the same :latest/:buildcache tags - cron staggered 06:00→04:00 Monday: it shared the exact minute with evals-periodic, which could race a half-pushed tag or duplicate the build - timeout-minutes: 30 (was unbounded → 360-min default for a hung docker build) Co-Authored-By: Claude Fable 5 --- .github/workflows/ci-image.yml | 45 +++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci-image.yml b/.github/workflows/ci-image.yml index 4cb1dccc2..19eceb94b 100644 --- a/.github/workflows/ci-image.yml +++ b/.github/workflows/ci-image.yml @@ -1,21 +1,33 @@ name: Build CI Image on: - # Rebuild weekly (Monday 6am UTC) to pick up CLI updates + # Rebuild weekly (Monday 4am UTC) to pick up CLI updates — deliberately 2h + # BEFORE evals-periodic's 6am cron so the weekly eval run finds a fresh + # image instead of racing a half-pushed tag or duplicating the build. schedule: - - cron: '0 6 * * 1' - # Rebuild on Dockerfile or lockfile changes + - cron: '0 4 * * 1' + # Rebuild on Dockerfile or lockfile changes. package.json is deliberately + # NOT a trigger: the tag hash below excludes it (its version field bumps on + # every ship), so a package.json-triggered run rebuilt and re-pushed the + # IDENTICAL tag on every merge to main (~2m26s each for zero content change). push: branches: [main] paths: - '.github/docker/Dockerfile.ci' - - 'package.json' - 'bun.lock' + - 'patches/**' # Manual trigger workflow_dispatch: +# Two rapid main pushes must not race pushing the same :latest/:buildcache +# tags; newest wins. +concurrency: + group: ci-image-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ubicloud-standard-8 + timeout-minutes: 30 permissions: contents: read packages: write @@ -25,9 +37,10 @@ jobs: # Copy lockfile + package.json into Docker build context - run: cp package.json bun.lock .github/docker/ && cp -R patches .github/docker/patches - # Same content-hash tag expression as evals.yml / evals-periodic.yml. - # This is the tag the eval matrix looks up first — without pushing it - # here, the weekly/main prebuild never warms the cache that matters. + # Same content-hash tag expression as evals.yml / evals-periodic.yml + # (byte-identity pinned by test/ci-image-tag-binding.test.ts). This is + # the tag the eval matrix looks up first — without pushing it here, the + # weekly/main prebuild never warms the cache that matters. - id: meta run: echo "tag=ghcr.io/${{ github.repository }}/ci:${{ hashFiles('.github/docker/Dockerfile.ci', 'bun.lock', 'patches/**') }}" >> "$GITHUB_OUTPUT" @@ -37,11 +50,25 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + # Skip the ~2.5min build when the content-hash tag already exists + # (mirrors evals.yml's check). The weekly cron still refreshes :latest + # via a full run when the tag is genuinely new. + - name: Check if image exists + id: check + run: | + if docker manifest inspect ${{ steps.meta.outputs.tag }} > /dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + # Registry cache export needs a docker-container builder — the default # `docker` driver hard-errors on cache-to. - - uses: docker/setup-buildx-action@v4 + - if: steps.check.outputs.exists == 'false' + uses: docker/setup-buildx-action@v4 - - uses: docker/build-push-action@v7 + - if: steps.check.outputs.exists == 'false' + uses: docker/build-push-action@v7 with: context: .github/docker file: .github/docker/Dockerfile.ci