guard: an empty channel killed the lane instead of passing it
Android / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Python tests (push) Successful in 9s
CI & Build / integration (push) Successful in 15s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m33s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m14s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Android / Kotlin + Rust (APK) (push) Successful in 7m46s
Android / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Python tests (push) Successful in 9s
CI & Build / integration (push) Successful in 15s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m33s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m14s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Android / Kotlin + Rust (APK) (push) Successful in 7m46s
The first merge to `main` took the Android lane down (run 4857): the decide job exited 1 in 0.16 seconds with no output at all, and the image build skipped behind it because a failing lane must not publish. `stable` had never published an APK, which the guard treats as a pass — there is nothing to go backwards from, and `[ -z "$published" ]` says so in a branch of its own. That branch was unreachable. `published="$(published_for ...)"` under `set -e` dies on the substitution before it, and everything the pipeline would have printed goes into the capture rather than the log. What decided which lookups had the bug is the last command in the pipeline. `sed` on empty input exits 0; `grep` exits 1. Three of the four end in `sed`. Android's version_code ends in `grep -oE '[0-9]+$'`, so it was the only one — and only on a channel with nothing on it, which is why a week of dev pushes never saw it. The tests now reach the half of the guard that talks to a feed, with `curl` shadowed on PATH so they stay hermetic: an empty channel passes and builds, a lower published version passes, a higher one fails the lane, and an equal Android code is refused because Android will not install it.
This commit is contained in:
@@ -79,19 +79,35 @@ fetch() {
|
||||
# What the channel is serving, per artifact. ONE definition of where to look, shared
|
||||
# with `should-build.sh` — the skip decision and the guard must agree about what is
|
||||
# published, and two readers of one fact is how this repo keeps producing #2181-2183.
|
||||
#
|
||||
# EVERY LOOKUP HERE MUST SUCCEED EVEN WHEN IT FINDS NOTHING. That is what the `|| true`
|
||||
# on each pipeline is for, and it is load-bearing rather than defensive noise.
|
||||
#
|
||||
# An empty channel is a REAL state this guard is written to pass — `[ -z "$published" ]`
|
||||
# further down says so in as many words. But the value is captured as
|
||||
# `published="$(published_for ...)"`, and under `set -e` a command substitution that
|
||||
# exits non-zero kills the script before that branch is ever reached. Silently, too:
|
||||
# everything the pipeline would have said went into the capture rather than the log.
|
||||
#
|
||||
# WHICH COMMAND THE PIPELINE HAPPENS TO END ON decides whether that fires, which is the
|
||||
# part worth remembering. `sed` on empty input exits 0; `grep` exits 1. Three of these
|
||||
# four lookups end in `sed` and were fine. The one that ends in `grep -oE '[0-9]+$'` —
|
||||
# Android's version_code — was not, and it failed the whole Android lane on the first
|
||||
# merge to `main` (run 4857): exit 1, no output, 0.16 seconds, on the one channel that
|
||||
# had no APK published yet. Its three neighbours hid it until then.
|
||||
published_for() {
|
||||
case "$1" in
|
||||
desktop)
|
||||
# What the UPDATER reads. The manifest is the thing that decides whether a
|
||||
# client is offered a build, so it is the authority on what is published.
|
||||
fetch "$SERVER/$REPO/releases/download/$2/latest.json" \
|
||||
{ fetch "$SERVER/$REPO/releases/download/$2/latest.json" \
|
||||
| grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 \
|
||||
| sed -E 's/.*"([^"]+)"$/\1/'
|
||||
| sed -E 's/.*"([^"]+)"$/\1/'; } || true
|
||||
;;
|
||||
android)
|
||||
fetch "$SERVER/$REPO/releases/download/$2/thoughtsync-android.json" \
|
||||
{ fetch "$SERVER/$REPO/releases/download/$2/thoughtsync-android.json" \
|
||||
| grep -oE '"version_code"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 \
|
||||
| grep -oE '[0-9]+$'
|
||||
| grep -oE '[0-9]+$'; } || true
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -104,9 +120,9 @@ published_name() {
|
||||
case "$1" in
|
||||
desktop) published_for desktop "$2" ;;
|
||||
android)
|
||||
fetch "$SERVER/$REPO/releases/download/$2/thoughtsync-android.json" \
|
||||
{ fetch "$SERVER/$REPO/releases/download/$2/thoughtsync-android.json" \
|
||||
| grep -oE '"version_name"[[:space:]]*:[[:space:]]*"[^"]+"' | head -1 \
|
||||
| sed -E 's/.*"([^"]+)"$/\1/'
|
||||
| sed -E 's/.*"([^"]+)"$/\1/'; } || true
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user