[object Object]

← back to Rentv 2026

fix(social): harden Vimeo refresh — empty-result guard + atomic write so a bad cron run can't clobber the deploy-protected library

7f6b537b6d6b538cbdd0a11dae8befa30093817c · 2026-07-31 15:17:16 -0700 · Steve Abrams

The daily prod cron writes data/vimeo-library.json, which .deploy.conf excludes
from rsync (no deploy to heal it). Two failure modes could silently corrupt the
live library: (1) a degenerate refresh (JWT ok, API returns []) overwriting the
good 123-video catalog with count:0; (2) a crash mid json.dump leaving the file
truncated. Now: bail (keep last-good) if 0 videos, and write via temp+os.replace.
Smoke-tested: 123 videos, clean atomic rename, no stray tmp.

Files touched

Diff

commit 7f6b537b6d6b538cbdd0a11dae8befa30093817c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 31 15:17:16 2026 -0700

    fix(social): harden Vimeo refresh — empty-result guard + atomic write so a bad cron run can't clobber the deploy-protected library
    
    The daily prod cron writes data/vimeo-library.json, which .deploy.conf excludes
    from rsync (no deploy to heal it). Two failure modes could silently corrupt the
    live library: (1) a degenerate refresh (JWT ok, API returns []) overwriting the
    good 123-video catalog with count:0; (2) a crash mid json.dump leaving the file
    truncated. Now: bail (keep last-good) if 0 videos, and write via temp+os.replace.
    Smoke-tested: 123 videos, clean atomic rename, no stray tmp.
---
 .gitignore                    |  3 +++
 scripts/pull-vimeo-library.py | 17 ++++++++++++++---
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index 60ea807b..610a54a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,6 @@ public/press/media/*.mp4
 # article-to-video studio artifacts (regenerable)
 data/social-jobs/
 public/social-videos/
+
+__pycache__/
+*.pyc
diff --git a/scripts/pull-vimeo-library.py b/scripts/pull-vimeo-library.py
index 6a753024..2acff269 100644
--- a/scripts/pull-vimeo-library.py
+++ b/scripts/pull-vimeo-library.py
@@ -84,10 +84,21 @@ def main():
         except Exception:
             v["thumb_local"] = ""
 
+    # Guard: a degenerate refresh (JWT ok but API returned nothing) must NOT clobber the
+    # last-good catalog. data/vimeo-library.json is deploy-excluded, so there's no deploy to
+    # heal a bad write — keep the previous file and fail loud for the cron log instead.
+    if not items:
+        sys.exit("refresh produced 0 videos — keeping last-good library, not overwriting")
+
     eps = sum(1 for i in items if i["kind"] == "episode")
-    json.dump({"source": f"https://vimeo.com/user{USER_ID}", "fetched_at": time.strftime("%Y-%m-%d"),
-               "count": len(items), "episodes": eps, "clips": len(items) - eps, "items": items},
-              open(OUT, "w"), indent=1)
+    payload = {"source": f"https://vimeo.com/user{USER_ID}", "fetched_at": time.strftime("%Y-%m-%d"),
+               "count": len(items), "episodes": eps, "clips": len(items) - eps, "items": items}
+    # Atomic write: fill a temp file then rename, so a crash mid-write can never leave the
+    # live (deploy-protected) library truncated.
+    tmp = OUT + ".tmp"
+    with open(tmp, "w") as f:
+        json.dump(payload, f, indent=1)
+    os.replace(tmp, OUT)
     print(f"vimeo-library.json: {len(items)} videos ({eps} episodes, {len(items)-eps} clips), +{new} new thumbs")
 
 

← d3c7c5c9 chore(social): daily prod cron to auto-refresh Vimeo library  ·  back to Rentv 2026  ·  feat(deals): add sort + density controls to the CRE deal gri fccef3ba →