[object Object]

← back to Beverlyhillsvideos

poster: harden for unattended ops (Cody terminal-gate FIX-FIRST)

49e3fa0cfb28e2fc8ced930e7a6b10fc5580a647 · 2026-08-07 06:50:16 -0700 · Steve Abrams

- reachable(): 5s AbortController timeout so a hung video server can't freeze a slot.
- watchdog: detect SILENT missed slots (post.log stale >8h = cadence stopped, no
  error line to catch it otherwise) + write data/latest.json heartbeat for the
  meta-watchdog; bumped daily→4x/day (2/8/14/20, offset 2h after each post slot).

Files touched

Diff

commit 49e3fa0cfb28e2fc8ced930e7a6b10fc5580a647
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Aug 7 06:50:16 2026 -0700

    poster: harden for unattended ops (Cody terminal-gate FIX-FIRST)
    
    - reachable(): 5s AbortController timeout so a hung video server can't freeze a slot.
    - watchdog: detect SILENT missed slots (post.log stale >8h = cadence stopped, no
      error line to catch it otherwise) + write data/latest.json heartbeat for the
      meta-watchdog; bumped daily→4x/day (2/8/14/20, offset 2h after each post slot).
---
 social/poster/post-daily.mjs      |  7 ++++++-
 social/poster/token-health-run.sh | 35 ++++++++++++++++++++++++++++++-----
 2 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/social/poster/post-daily.mjs b/social/poster/post-daily.mjs
index 7b9f475..9b8309d 100644
--- a/social/poster/post-daily.mjs
+++ b/social/poster/post-daily.mjs
@@ -54,11 +54,16 @@ const verticalUrl = (film) =>
     : `${cfg.siteBase}${film.src.replace('/video/', '/video/vertical/')}`;
 
 async function reachable(url) {
+  // 5s abort so a hung (non-refusing) server can't freeze the whole posting slot.
+  const ac = new AbortController();
+  const t = setTimeout(() => ac.abort(), 5000);
   try {
-    const res = await fetch(url, { method: 'HEAD' });
+    const res = await fetch(url, { method: 'HEAD', signal: ac.signal });
     return res.ok && (res.headers.get('content-type') || '').includes('video');
   } catch {
     return false;
+  } finally {
+    clearTimeout(t);
   }
 }
 
diff --git a/social/poster/token-health-run.sh b/social/poster/token-health-run.sh
index ab113bc..c16f07b 100755
--- a/social/poster/token-health-run.sh
+++ b/social/poster/token-health-run.sh
@@ -1,11 +1,36 @@
 #!/bin/zsh
-# Daily BHV posting watchdog: token health + recent post failures. Alerts via CNCP on trouble.
+# BHV posting watchdog: (1) token health, (2) recent live-post failures, (3) SILENT
+# missed-slot detection — if the 6h cadence stopped firing (Mac2 asleep/launchd dead),
+# no error line is ever written, so we detect it by post.log going stale. Alerts via CNCP.
+# Writes data/latest.json each run as a heartbeat the dw-canary-meta-watchdog can watch.
 cd "$(dirname "$0")/../.." || exit 1
+LOG=social/poster/post.log
+mkdir -p social/poster/data
+
 OUT=$(/opt/homebrew/bin/node social/poster/token-health.mjs 2>&1); RC=$?
-# also flag if the last few post.log runs recorded a live POST FAILED
-FAILS=$(tail -40 social/poster/post.log 2>/dev/null | grep -c "POST FAILED")
-if [[ $RC -ne 0 || $FAILS -gt 0 ]]; then
-  MSG="BHV Instagram watchdog: token=[$OUT] recentPostFailures=$FAILS"
+FAILS=$(tail -60 "$LOG" 2>/dev/null | grep -c "POST FAILED")
+
+# Age (hours) of the newest scheduled run header ("----- <ts> · slot=... -----").
+STALE_H=$(/opt/homebrew/bin/node -e '
+try{
+  const fs=require("fs");
+  const lines=fs.readFileSync("social/poster/post.log","utf8").trim().split("\n").filter(l=>l.startsWith("----- "));
+  if(!lines.length){console.log(999);process.exit(0);}
+  const m=lines[lines.length-1].match(/----- (.+?) (?:PDT|PST|[A-Z]{2,4}) ·/);
+  const t=m?Date.parse(m[1].replace(" ","T")):NaN;
+  console.log(isNaN(t)?999:((Date.now()-t)/3.6e6).toFixed(1));
+}catch{console.log(999);}
+' 2>/dev/null)
+# 6h cadence + grace: >8h since the last run means a slot was silently missed.
+MISSED=$(/opt/homebrew/bin/node -e "console.log(parseFloat('${STALE_H:-999}')>8?1:0)" 2>/dev/null)
+
+STATE="HEALTHY"
+if [[ $RC -ne 0 || $FAILS -gt 0 || $MISSED -eq 1 ]]; then STATE="ALERT"; fi
+printf '{"ts":"%s","state":"%s","recentFailures":%s,"hoursSinceLastRun":%s}\n' \
+  "$(date -u +%FT%TZ)" "$STATE" "${FAILS:-0}" "${STALE_H:-999}" > social/poster/data/latest.json
+
+if [[ "$STATE" == "ALERT" ]]; then
+  MSG="BHV IG watchdog: token=[$OUT] recentFailures=$FAILS hoursSinceLastPost=${STALE_H} missedSlot=$MISSED"
   curl -s -X POST http://127.0.0.1:3333/api/parking-lot -H 'Content-Type: application/json' \
     -d "{\"project\":\"beverlyhillsvideos\",\"title\":\"BHV posting needs attention\",\"note\":\"$MSG\"}" >/dev/null 2>&1 || true
   echo "$(date) ALERT: $MSG" >> social/poster/watchdog.log

← c461668 BHV watcher: fail-loud (heartbeat latest.json + CNCP alert o  ·  back to Beverlyhillsvideos  ·  auto-data-snapshot: 2026-08-07T06:58:19 (2 data files) — soc afa1634 →