ci: a force_build escape hatch for the path skip-if-exists hides (326 step 3)
Build images / sign-extension (push) Successful in 3s
Build images / build-ml (push) Successful in 7s
Build images / build-agent (push) Successful in 6s
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 29s
Build images / build-web (push) Successful in 7s
extension / lint (push) Successful in 18s
CI / integration (push) Successful in 3m47s
Build images / sign-extension (push) Successful in 3s
Build images / build-ml (push) Successful in 7s
Build images / build-agent (push) Successful in 6s
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 29s
Build images / build-web (push) Successful in 7s
extension / lint (push) Successful in 18s
CI / integration (push) Successful in 3m47s
`workflow_dispatch` with a `force_build` boolean, honoured inside each of the three reuse steps. It exists because skip-if-exists made its own build path untestable. `agent/` has not changed since 2026-07-17, so the agent build has correctly declined to run on every push since — which also means #3190, whose whole symptom lives on that path, cannot be reproduced on demand. Editing build.yml does not force a build either, and that is deliberate: the workflow is not shipped bytes, so it is in no artifact's path set, and putting it in one would re-version every artifact for a comment change. That is also why this lands before step 2 rather than after. Step 1 moved the builds onto a container driver and turned attestations off; the claim that `fc.revision` still reads back cannot be checked until something actually builds under that driver. Run 4887 confirmed only the cheaper half — `Set up buildx` succeeded on all three jobs, so the buildkit sibling container does start against the mounted socket. Details worth keeping: * FORCE is checked in the reuse step, not in the build step's `if:`. The repoint step keys off `hit` too, and a force that bypassed only the build would leave the two disagreeing about what had happened. * `github.event.inputs`, not the `inputs` context — release.yml already uses that form and it is the one this runner is known to evaluate. Read through env rather than interpolated into the run block, same as release.yml's TAG. * One input, not one per artifact. Three booleans is an interface nobody remembers. Scribe #3252, #3249. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,26 @@ on:
|
||||
# Releases still happen (rule 148, on explicit request per rule 2). They
|
||||
# produce a changelog, not an image.
|
||||
|
||||
# The escape hatch for the one thing skip-if-exists makes untestable: a
|
||||
# build that WOULD be skipped. `agent/` has not changed since 2026-07-17, so
|
||||
# every push since has correctly declined to build it — which also means the
|
||||
# agent build path has not run in six weeks and cannot be exercised on
|
||||
# demand. #3190 lives on exactly that path.
|
||||
#
|
||||
# Editing build.yml does not force one either, and that is deliberate: the
|
||||
# workflow is not shipped bytes, so it is in no artifact's path set. Putting
|
||||
# it in one would re-version every artifact for a comment change.
|
||||
#
|
||||
# ONE input, not one per artifact. Forcing all three is cheap once the
|
||||
# registry cache is warm (#3114), and three booleans is an interface nobody
|
||||
# remembers the meaning of.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_build:
|
||||
description: 'Rebuild every image even if the published revision matches'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# Requires repo secret RELEASE_TOKEN — a Forgejo PAT with scopes:
|
||||
# - write:package, read:package (for docker push to git.fabledsword.com)
|
||||
# - write:release (for ext-<version> release asset cache)
|
||||
@@ -498,6 +518,12 @@ jobs:
|
||||
env:
|
||||
IMAGE: git.fabledsword.com/bvandeusen/fabledcurator
|
||||
CHANNEL: ${{ steps.tag.outputs.channel }}
|
||||
# Empty on a push; the string "true" only from a workflow_dispatch
|
||||
# that asked for it. `github.event.inputs` rather than the `inputs`
|
||||
# context — release.yml already uses that form, and it is the one
|
||||
# this runner is known to evaluate. Read through env rather than
|
||||
# interpolated into the run block, same rule as release.yml's TAG.
|
||||
FORCE: ${{ github.event.inputs.force_build }}
|
||||
run: |
|
||||
set -eu
|
||||
DERIVED=$(sh scripts/artifacts.sh revision web)
|
||||
@@ -537,7 +563,14 @@ jobs:
|
||||
echo "reuse: NOTE tag is being index-wrapped and reuse is dead."
|
||||
fi
|
||||
|
||||
if [ -n "$PUBLISHED" ] && [ "$PUBLISHED" = "$DERIVED" ]; then
|
||||
# FORCE is checked here rather than in the build step's `if:`, so
|
||||
# that one decision drives everything downstream. The repoint step
|
||||
# keys off `hit` too, and a force that bypassed only the build would
|
||||
# leave the two disagreeing about what just happened.
|
||||
if [ "${FORCE:-false}" = "true" ]; then
|
||||
echo "hit=false" >> "$GITHUB_OUTPUT"
|
||||
echo "reuse: force_build set — building regardless"
|
||||
elif [ -n "$PUBLISHED" ] && [ "$PUBLISHED" = "$DERIVED" ]; then
|
||||
echo "hit=true" >> "$GITHUB_OUTPUT"
|
||||
echo "reuse: already published — skipping the build"
|
||||
else
|
||||
@@ -863,6 +896,12 @@ jobs:
|
||||
env:
|
||||
IMAGE: git.fabledsword.com/bvandeusen/fabledcurator-ml
|
||||
CHANNEL: ${{ steps.tag.outputs.channel }}
|
||||
# Empty on a push; the string "true" only from a workflow_dispatch
|
||||
# that asked for it. `github.event.inputs` rather than the `inputs`
|
||||
# context — release.yml already uses that form, and it is the one
|
||||
# this runner is known to evaluate. Read through env rather than
|
||||
# interpolated into the run block, same rule as release.yml's TAG.
|
||||
FORCE: ${{ github.event.inputs.force_build }}
|
||||
run: |
|
||||
set -eu
|
||||
DERIVED=$(sh scripts/artifacts.sh revision ml)
|
||||
@@ -898,7 +937,14 @@ jobs:
|
||||
echo "reuse: NOTE tag is being index-wrapped and reuse is dead."
|
||||
fi
|
||||
|
||||
if [ -n "$PUBLISHED" ] && [ "$PUBLISHED" = "$DERIVED" ]; then
|
||||
# FORCE is checked here rather than in the build step's `if:`, so
|
||||
# that one decision drives everything downstream. The repoint step
|
||||
# keys off `hit` too, and a force that bypassed only the build would
|
||||
# leave the two disagreeing about what just happened.
|
||||
if [ "${FORCE:-false}" = "true" ]; then
|
||||
echo "hit=false" >> "$GITHUB_OUTPUT"
|
||||
echo "reuse: force_build set — building regardless"
|
||||
elif [ -n "$PUBLISHED" ] && [ "$PUBLISHED" = "$DERIVED" ]; then
|
||||
echo "hit=true" >> "$GITHUB_OUTPUT"
|
||||
echo "reuse: already published — skipping the build"
|
||||
else
|
||||
@@ -1140,6 +1186,12 @@ jobs:
|
||||
env:
|
||||
IMAGE: git.fabledsword.com/bvandeusen/fabledcurator-agent
|
||||
CHANNEL: ${{ steps.tag.outputs.channel }}
|
||||
# Empty on a push; the string "true" only from a workflow_dispatch
|
||||
# that asked for it. `github.event.inputs` rather than the `inputs`
|
||||
# context — release.yml already uses that form, and it is the one
|
||||
# this runner is known to evaluate. Read through env rather than
|
||||
# interpolated into the run block, same rule as release.yml's TAG.
|
||||
FORCE: ${{ github.event.inputs.force_build }}
|
||||
run: |
|
||||
set -eu
|
||||
DERIVED=$(sh scripts/artifacts.sh revision agent)
|
||||
@@ -1175,7 +1227,14 @@ jobs:
|
||||
echo "reuse: NOTE tag is being index-wrapped and reuse is dead."
|
||||
fi
|
||||
|
||||
if [ -n "$PUBLISHED" ] && [ "$PUBLISHED" = "$DERIVED" ]; then
|
||||
# FORCE is checked here rather than in the build step's `if:`, so
|
||||
# that one decision drives everything downstream. The repoint step
|
||||
# keys off `hit` too, and a force that bypassed only the build would
|
||||
# leave the two disagreeing about what just happened.
|
||||
if [ "${FORCE:-false}" = "true" ]; then
|
||||
echo "hit=false" >> "$GITHUB_OUTPUT"
|
||||
echo "reuse: force_build set — building regardless"
|
||||
elif [ -n "$PUBLISHED" ] && [ "$PUBLISHED" = "$DERIVED" ]; then
|
||||
echo "hit=true" >> "$GITHUB_OUTPUT"
|
||||
echo "reuse: already published — skipping the build"
|
||||
else
|
||||
|
||||
@@ -146,6 +146,16 @@ per `docs/process.md`'s "add deps to the image when used by >1 project".
|
||||
rebuild forever with every lane green. Same failure as #3183, different door.
|
||||
These jobs run inside a container against a mounted docker socket, so the
|
||||
buildkit container is a sibling rather than a child.
|
||||
- **`build.yml` accepts a `workflow_dispatch` with `force_build`**, which
|
||||
bypasses the reuse check for all three images. It exists because
|
||||
skip-if-exists makes its own build path untestable: `agent/` has not changed
|
||||
since 2026-07-17, so the agent build has not run in six weeks and cannot be
|
||||
exercised on demand — and #3190 lives on exactly that path. Editing
|
||||
`build.yml` does not force a build either, deliberately: the workflow is not
|
||||
shipped bytes and is in no artifact's path set. The flag is read through
|
||||
`github.event.inputs` into an env var rather than interpolated into a run
|
||||
block, and it is checked inside the reuse step so that one decision drives
|
||||
both the build and the repoint.
|
||||
- **`FC_CHANNEL` and `FC_VERSION` are build args, not runtime settings.**
|
||||
`build.yml` passes them to the web image only — the ml and agent images have
|
||||
nothing to report them to. `/api/health` returns both, the foot of Settings
|
||||
|
||||
Reference in New Issue
Block a user