ci: a boolean input never equals the string 'true'
Build images / sign-extension (push) Successful in 4s
Build images / build-ml (push) Successful in 6s
Build images / build-agent (push) Successful in 7s
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 2s
CI / frontend-build (push) Successful in 23s
CI / backend-lint-and-test (push) Successful in 35s
Build images / build-web (push) Successful in 8s
CI / integration (push) Successful in 1m48s
Build images / sign-extension (push) Successful in 4s
Build images / build-ml (push) Successful in 6s
Build images / build-agent (push) Successful in 7s
CI / lint (push) Successful in 3s
CI / extension-version (push) Successful in 2s
CI / frontend-build (push) Successful in 23s
CI / backend-lint-and-test (push) Successful in 35s
Build images / build-web (push) Successful in 8s
CI / integration (push) Successful in 1m48s
The refresh lever did not work, and the way it did not work is the point.
Run 5270 dispatched with refresh=true. Its log:
expression '(github.event_name == 'schedule'
|| github.event.inputs.refresh == 'true') && 'true' || 'false''
evaluated to '%!t(string=false)'
trigger: event=workflow_dispatch IS_REFRESH='false' BUILD_REF='refs/heads/dev'
trigger: raw inputs refresh='true' force_build='false'
The input arrived as true and the comparison still said false. `type: boolean`
delivers a real boolean, and GitHub expression semantics cast operands to
numbers when their types differ — so `true == 'true'` compares 1 against NaN.
My comment on the previous commit asserted the opposite, that Forgejo delivers
inputs as strings, and asserted it without checking.
The run went GREEN with every step skipped, because a refresh that evaluates
false is indistinguishable from an ordinary push. A lever that silently does
nothing is worse than no lever: it would have been trusted.
Normalised through format(), which is representation-independent — a boolean
true and a string 'true' both render 'true'. That is also why force_build was
never bitten: it passes its raw value into an env var and compares in the
shell, where everything is a string already. format() buys the same thing at
expression level, which is where a step `if:` needs the answer.
The diagnostic from the previous commit stays. It is what turned this from a
guess into a measurement, and it is the only thing that would catch the same
class of failure next time.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTjbZZ6JirCMSaJzQV1RhA
This commit is contained in:
@@ -89,12 +89,30 @@ on:
|
||||
# makes the milestone-362 gate verifiable at all: a gate has to be watched
|
||||
# rejecting something before anyone can believe it is wired up.
|
||||
#
|
||||
# Note this is a STRING comparison, not a boolean. Forgejo delivers
|
||||
# workflow_dispatch inputs as strings, so `inputs.refresh` is 'true'/'false'
|
||||
# and `&&` on it would treat the string 'false' as truthy.
|
||||
# The input is normalised through `format()` before it is compared, and that
|
||||
# is not defensive styling — the direct comparison is WRONG and fails silently.
|
||||
#
|
||||
# `type: boolean` delivers a real boolean, and GitHub expression semantics cast
|
||||
# operands to numbers when their types differ: `true == 'true'` compares 1
|
||||
# against NaN and is FALSE. Measured on run 5270, whose own log says it —
|
||||
#
|
||||
# expression '(github.event_name == 'schedule'
|
||||
# || github.event.inputs.refresh == 'true') && 'true' || 'false''
|
||||
# evaluated to '%!t(string=false)'
|
||||
# trigger: raw inputs refresh='true'
|
||||
#
|
||||
# — the input arrived as `true` and the expression still said false. The run
|
||||
# then went green with every step skipped, because a refresh that evaluates
|
||||
# false behaves exactly like an ordinary push. That is the whole hazard: the
|
||||
# failure has no symptom.
|
||||
#
|
||||
# `force_build` never hit this because it never compares in an expression. It
|
||||
# passes the raw value into an env var and tests it in the shell, where
|
||||
# everything is already a string. `format('{0}', x)` buys the same thing here,
|
||||
# where a step-level `if:` needs the answer before any shell runs.
|
||||
env:
|
||||
IS_REFRESH: ${{ (github.event_name == 'schedule' || github.event.inputs.refresh == 'true') && 'true' || 'false' }}
|
||||
BUILD_REF: ${{ (github.event_name == 'schedule' || github.event.inputs.refresh == 'true') && 'main' || github.ref }}
|
||||
IS_REFRESH: ${{ (github.event_name == 'schedule' || format('{0}', github.event.inputs.refresh) == 'true') && 'true' || 'false' }}
|
||||
BUILD_REF: ${{ (github.event_name == 'schedule' || format('{0}', github.event.inputs.refresh) == 'true') && 'main' || github.ref }}
|
||||
|
||||
# Requires repo secret RELEASE_TOKEN — a Forgejo PAT with scopes:
|
||||
# - write:package, read:package (for docker push to git.fabledsword.com)
|
||||
|
||||
Reference in New Issue
Block a user