Repairs the main build broken by PR #129. CI green on 88508b53.
What broke
The first main build after the version rework failed on "Bundle latest release APK". :latest was never moved — "Build and push" was skipped — so nothing reached production, but every subsequent main push would have failed identically.
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 a grep matching nothing kills the assignment even though head succeeded. The step died at the assignment, before reaching the if written to handle the empty case.
The case it died on is the one PR #129 described and then failed to handle in code: v2026.09.09 predates sidecar assets, so its .apk.version grep matched nothing.
The worse thing the audit found
The other two assignments in that step carried the same 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 reachable under pipefail. That predates this batch entirely.
All three now carry || true.
Verification
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, because the degradation depends on it and the failure only surfaces on the one release that triggers it — the new test asserts every command-substitution grep in that step ends with || true, falsified by removing it from the sidecar line.
Unrelated, and not fixed here
Run 6270 (android lane) failed separately with lstat /root/.cache/act/…/jest.config.ts: no such file or directory — a corrupted actions/cache copy on the runner host, failing 60ms in on a file that does not exist in this repo. Runner infrastructure, not code.
Repairs the `main` build broken by PR #129. CI green on `88508b53`.
## What broke
The first `main` build after the version rework failed on "Bundle latest release APK". `:latest` was never moved — "Build and push" was skipped — so nothing reached production, but every subsequent `main` push would have failed identically.
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 a grep matching nothing kills the assignment even though `head` succeeded. The step died *at the assignment*, before reaching the `if` written to handle the empty case.
The case it died on is the one PR #129 described and then failed to handle in code: `v2026.09.09` predates sidecar assets, so its `.apk.version` grep matched nothing.
## The worse thing the audit found
The other two assignments in that step carried the same 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 reachable** under pipefail. That predates this batch entirely.
All three now carry `|| true`.
## Verification
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, because the degradation depends on it and the failure only surfaces on the one release that triggers it — the new test asserts every command-substitution grep in that step ends with `|| true`, falsified by removing it from the sidecar line.
## Unrelated, and not fixed here
Run 6270 (android lane) failed separately with `lstat /root/.cache/act/…/jest.config.ts: no such file or directory` — a corrupted `actions/cache` copy on the runner host, failing 60ms in on a file that does not exist in this repo. Runner infrastructure, not code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Repairs the
mainbuild broken by PR #129. CI green on88508b53.What broke
The first
mainbuild after the version rework failed on "Bundle latest release APK".:latestwas never moved — "Build and push" was skipped — so nothing reached production, but every subsequentmainpush would have failed identically.The runner invokes
shell: bashasbash -e -o pipefail. Under pipefail a command substitution reports the first non-zero status in its pipeline, not the last, so a grep matching nothing kills the assignment even thoughheadsucceeded. The step died at the assignment, before reaching theifwritten to handle the empty case.The case it died on is the one PR #129 described and then failed to handle in code:
v2026.09.09predates sidecar assets, so its.apk.versiongrep matched nothing.The worse thing the audit found
The other two assignments in that step carried the same hazard and had simply never fired, because a release always has a
tag_nameand an.apkasset. 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 reachable under pipefail. That predates this batch entirely.All three now carry
|| true.Verification
Reproduced under the runner's exact shell before fixing: without
|| truethe 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, because the degradation depends on it and the failure only surfaces on the one release that triggers it — the new test asserts every command-substitution grep in that step ends with
|| true, falsified by removing it from the sidecar line.Unrelated, and not fixed here
Run 6270 (android lane) failed separately with
lstat /root/.cache/act/…/jest.config.ts: no such file or directory— a corruptedactions/cachecopy on the runner host, failing 60ms in on a file that does not exist in this repo. Runner infrastructure, not code.🤖 Generated with Claude Code
https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH