From 8a75e5f340c88c05753b79a7ac0a721475bafbb4 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 30 Aug 2026 13:21:03 -0400 Subject: [PATCH] clients: an unquoted 1.0.3504551 is not JSON, and every sidecar was one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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.`, 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 --- packaging/fetch-clients.sh | 7 ++++++- src/thoughtsync/client_dist.py | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packaging/fetch-clients.sh b/packaging/fetch-clients.sh index 480cdb9..4b96266 100755 --- a/packaging/fetch-clients.sh +++ b/packaging/fetch-clients.sh @@ -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.` — 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" } diff --git a/src/thoughtsync/client_dist.py b/src/thoughtsync/client_dist.py index a85c6c0..6cba4be 100644 --- a/src/thoughtsync/client_dist.py +++ b/src/thoughtsync/client_dist.py @@ -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