fix(release): a non-matching grep must not kill the rebundle step
test-go / test (push) Successful in 1m39s
release / Build signed APK (releases and dev) (push) Successful in 6m11s
release / Build + push container image (push) Successful in 14s
release / Verify release artifacts (tag releases only) (push) Skipped
test-go / integration (push) Successful in 5m59s
test-go / test (push) Successful in 1m39s
release / Build signed APK (releases and dev) (push) Successful in 6m11s
release / Build + push container image (push) Successful in 14s
release / Verify release artifacts (tag releases only) (push) Skipped
test-go / integration (push) Successful in 5m59s
The first `main` build after the version rework failed, and the bug was
mine. :latest was never moved — "Build and push" was skipped — so nothing
reached production, but every subsequent main push would have failed the
same way.
The runner invokes `shell: bash` as `bash -e -o pipefail`. Under pipefail a
command substitution reports the FIRST non-zero status in its pipeline, not
the last, so
VAR="$(printf ... | grep -oP ... | grep -E '\.apk\.version$' | head -1)"
exits non-zero when that grep matches nothing, even though `head` succeeded.
With -e the step dies AT THE ASSIGNMENT — before reaching the `if` written
to handle exactly the empty case.
Which is what happened: v2026.09.09 predates sidecar assets, so its
`.apk.version` grep matched nothing and the step aborted instead of falling
through to the name-only branch I added in 9f3e0b8c for precisely that
release. The transition case was described correctly in that commit message
and then not handled in code.
The other two assignments carried the same latent hazard and had simply
never fired, because a release always has a tag_name and an .apk asset. So
the step's documented promise — "degrades to an empty client/ (404 update
channel) — never a wrong version — if no release or APK asset can be
resolved" — was never actually reachable under pipefail. All three now
carry `|| true`.
Reproduced under the runner's exact shell before fixing: without `|| true`
the script exits 1 with no output at all, proving it never reaches the
branch; with it, the fallback runs and emits the name-only sidecar.
Guarded, since the graceful degradation depends on this and the failure is
invisible until the one release that triggers it: the new test asserts every
command-substitution grep in that step ends with `|| true`, and was
falsified by removing it from the sidecar line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
This commit is contained in:
@@ -421,7 +421,10 @@ jobs:
|
||||
# the sidecar published beside it, so what the server reports is what
|
||||
# that build actually recorded rather than something re-derived here.
|
||||
# Degrades to an empty client/ (404 update channel) — never a wrong
|
||||
# version — if no release or APK asset can be resolved.
|
||||
# version — if no release or APK asset can be resolved. That
|
||||
# degradation only actually works because the greps below carry
|
||||
# `|| true`; under the runner's default pipefail a non-matching grep
|
||||
# kills the step instead of falling through to the empty-case branch.
|
||||
if: steps.guard.outputs.ready == 'true' && github.ref == 'refs/heads/main'
|
||||
shell: bash
|
||||
env:
|
||||
@@ -434,8 +437,15 @@ jobs:
|
||||
if [ -z "${REL_JSON}" ]; then
|
||||
echo "::notice::no published release — image ships without bundled APK"; exit 0
|
||||
fi
|
||||
TAG="$(printf '%s' "${REL_JSON}" | grep -oP '"tag_name":\s*"\K[^"]+' | head -1)"
|
||||
APK_URL="$(printf '%s' "${REL_JSON}" | grep -oP '"browser_download_url":\s*"\K[^"]+' | grep -E '\.apk$' | head -1)"
|
||||
# `|| true` on every one of these, and it is load-bearing rather
|
||||
# than defensive habit. The runner already invokes this shell as
|
||||
# `bash -e -o pipefail`, so a pipeline whose grep matches NOTHING
|
||||
# exits non-zero even though `head` succeeded — and the step dies at
|
||||
# the assignment, before ever reaching the `if` written to handle the
|
||||
# empty case. Every "degrades gracefully" branch below is unreachable
|
||||
# without this.
|
||||
TAG="$(printf '%s' "${REL_JSON}" | grep -oP '"tag_name":\s*"\K[^"]+' | head -1)" || true
|
||||
APK_URL="$(printf '%s' "${REL_JSON}" | grep -oP '"browser_download_url":\s*"\K[^"]+' | grep -E '\.apk$' | head -1)" || true
|
||||
if [ -z "${TAG}" ] || [ -z "${APK_URL}" ]; then
|
||||
echo "::notice::latest release '${TAG:-?}' has no APK asset — image ships without bundled APK"; exit 0
|
||||
fi
|
||||
@@ -446,7 +456,7 @@ jobs:
|
||||
# the formula lived in two files that had to be kept in step, and it
|
||||
# could only ever recover the name — the ordering key is build-time
|
||||
# minutes and does not exist anywhere after that build ends.
|
||||
SIDECAR_URL="$(printf '%s' "${REL_JSON}" | grep -oP '"browser_download_url":\s*"\K[^"]+' | grep -E '\.apk\.version$' | head -1)"
|
||||
SIDECAR_URL="$(printf '%s' "${REL_JSON}" | grep -oP '"browser_download_url":\s*"\K[^"]+' | grep -E '\.apk\.version$' | head -1)" || true
|
||||
if [ -n "${SIDECAR_URL}" ]; then
|
||||
curl -fsSL -H "Authorization: token ${CI_TOKEN}" -o client/minstrel.apk.version "${SIDECAR_URL}"
|
||||
cat client/minstrel.apk.version
|
||||
|
||||
Reference in New Issue
Block a user