From 678d7de94833d98223d97b52b1dc099ff70c8164 Mon Sep 17 00:00:00 2001 From: jaxperro Date: Thu, 23 Jul 2026 09:34:52 -0400 Subject: [PATCH] =?UTF-8?q?sync=5Ftape:=20never=20bless=20a=20timed-out=20?= =?UTF-8?q?sftp=20partial=20=E2=80=94=20fall=20through=20to=20the=20base64?= =?UTF-8?q?=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A TimeoutExpired get leaves a size>0 partial that the exists-check treated as success, so the console fallback never ran for timeouts; the caller's parquet read then failed (no magic bytes), deleted the file, and the next run repeated the identical loop — 8 hours of tape-head stall overnight 2026-07-23. Partials from timeouts are now removed and the fallback runs. Co-Authored-By: Claude Fable 5 --- recorder/sync_tape.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/recorder/sync_tape.py b/recorder/sync_tape.py index d060bd32..3759002f 100644 --- a/recorder/sync_tape.py +++ b/recorder/sync_tape.py @@ -48,13 +48,17 @@ def sftp_get(remote, local, timeout=240): (2026-07-22: sftp timeouts left magic-byte-less partial parquets while console commands stayed fast). Integrity is the caller's manifest row-count verify either way; partial files are removed here.""" + timed_out = False try: subprocess.run([FLYCTL, "ssh", "sftp", "get", remote, local, "-a", APP], capture_output=True, timeout=timeout, stdin=subprocess.DEVNULL) except subprocess.TimeoutExpired: - pass - if os.path.exists(local) and os.path.getsize(local) > 0: + timed_out = True # a timed-out get leaves a size>0 PARTIAL — must + # not be blessed, or the base64 fallback never + # runs and the file loops unreadable forever + # (2026-07-23: 8h of exactly that) + if not timed_out and os.path.exists(local) and os.path.getsize(local) > 0: return True try: os.remove(local)