From a3e1ea2b44e4a4130b6de0cd8f6f092a2fd81bb2 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 2 Jul 2026 23:05:27 +0000 Subject: [PATCH] fix(android/ci): retry transient Play API failures in play-upload.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The uploader only caught HTTPError — a URLError (TLS "EOF occurred in violation of protocol", the failure that dropped two release uploads on 2026-07-02) or a Google 5xx killed the job outright. Retry those with 3/9/27 s backoff; 4xx still fails fast. The edits API is transactional until commit, so re-sending is safe. Co-Authored-By: Claude Fable 5 --- clients/android/ci/play-upload.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/clients/android/ci/play-upload.py b/clients/android/ci/play-upload.py index 3e51046..db3d9bb 100644 --- a/clients/android/ci/play-upload.py +++ b/clients/android/ci/play-upload.py @@ -37,13 +37,30 @@ def call(method, url, token=None, data=None, content_type=None, want_json=True): headers["Authorization"] = f"Bearer {token}" if content_type: headers["Content-Type"] = content_type - req = urllib.request.Request(url, data=data, method=method, headers=headers) - try: - with urllib.request.urlopen(req, timeout=300) as r: - body = r.read() - except urllib.error.HTTPError as e: - raise ApiError(e.code, method, url, e.read().decode("utf-8", "replace")) - return json.loads(body) if (want_json and body) else body + # Transient-fault retries: googleapis.com occasionally drops the TLS session ("EOF + # occurred in violation of protocol" — failed two release uploads on 2026-07-02) or + # answers 5xx. Retry those with backoff; 4xx raises immediately (a real API error). + # The edits API is transactional until commit, so re-sending any of these is safe. + last = None + for attempt in range(4): + if attempt: + delay = 3**attempt + print(f"transient Play API failure ({last}); retry {attempt}/3 in {delay}s") + time.sleep(delay) + req = urllib.request.Request(url, data=data, method=method, headers=headers) + try: + with urllib.request.urlopen(req, timeout=300) as r: + body = r.read() + return json.loads(body) if (want_json and body) else body + except urllib.error.HTTPError as e: + if e.code >= 500: + last = f"HTTP {e.code}" + continue + raise ApiError(e.code, method, url, e.read().decode("utf-8", "replace")) + except urllib.error.URLError as e: + last = str(getattr(e, "reason", e)) + continue + sys.exit(f"ERROR: {method} {url} still failing after retries: {last}") def load_sa():