clients: an unquoted 1.0.3504551 is not JSON, and every sidecar was one
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 16s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m0s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m27s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m18s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 16s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m0s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m27s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m18s
`fetch-clients.sh` wrote `"version_code": %s` unquoted, which was right when the only ordering key in sight was Android's integer. The desktop's is Tauri's `1.0.<minutes>`, and unquoted that is not valid JSON at all — so `json.loads` raised on all four generated sidecars and the server advertised nothing. A silent zero, not an error: `_read` treats a malformed sidecar as "no client here", which is right for a corrupt drop-in and indistinguishable from this. Caught by running the real fetch against the live dev channel and feeding the result to the real resolver, rather than by reading the printf. Also makes `_resolve` wrap BOTH candidate roots in Path(). Only the first was, and the asymmetry fails the same quiet way: a str `/` str raises TypeError, `_read` catches it, and a perfectly good directory reads as empty. The whole pipeline now resolves end to end against the live channel — five of five platforms, every sidecar valid JSON, one human-readable version across all of them with each artifact keeping its own comparator type: android 2026.08.30.1711 code=3504552 57.6 MB linux-appimage 2026.08.30.1711 code='1.0.3504551' 95.3 MB signed linux-deb 2026.08.30.1711 code='1.0.3504551' 3.3 MB linux-pacman 2026.08.30.1711 code='1.0.3504551' 2.7 MB windows 2026.08.30.1711 code='1.0.3504551' 2.6 MB
This commit is contained in:
@@ -69,7 +69,12 @@ digest() { sha256sum "$1" | cut -d' ' -f1; }
|
||||
# whole one.
|
||||
sidecar() {
|
||||
_file="$1"; _out="$2"; _name="$3"; _code="$4"
|
||||
printf '{\n "version_name": "%s",\n "version_code": %s,\n "size": %s,\n "sha256": "%s"\n}\n' \
|
||||
# `version_code` is QUOTED here, and that is not a slip. This function only ever
|
||||
# writes DESKTOP sidecars, whose ordering key is Tauri's `1.0.<minutes>` — which
|
||||
# unquoted is not valid JSON at all, so every sidecar this wrote would fail to
|
||||
# parse and the server would advertise nothing. Android's sidecar is a different
|
||||
# file, copied verbatim from its lane, and keeps its integer.
|
||||
printf '{\n "version_name": "%s",\n "version_code": "%s",\n "size": %s,\n "sha256": "%s"\n}\n' \
|
||||
"$_name" "$_code" "$(bytes "$_file")" "$(digest "$_file")" > "$_out"
|
||||
}
|
||||
|
||||
|
||||
@@ -263,7 +263,10 @@ def _resolve(platform: Platform) -> tuple[Path, dict] | None:
|
||||
broken or half-copied pair does NOT shadow the image: it simply is not a
|
||||
client, so the search moves on.
|
||||
"""
|
||||
for root in (Path(Config.client_root()), BAKED_ROOT):
|
||||
# Both wrapped in Path(), not just the first. The asymmetry was arbitrary and it
|
||||
# fails quietly: `_read` catches the TypeError a str `/` str raises and reports
|
||||
# "no client here", so a directory that is perfectly fine reads as empty.
|
||||
for root in (Path(Config.client_root()), Path(BAKED_ROOT)):
|
||||
release = _read(root, platform)
|
||||
if release is not None:
|
||||
return root, release
|
||||
|
||||
Reference in New Issue
Block a user