[object Object]

← back to Wallco Ai

night-builder: debug-log url-patch step + ship self-healing watchdog

2ea2f75dde4ee126151cc68af32d9b00e4ac1126 · 2026-05-20 07:43:38 -0700 · Steve Abrams

V2 fix (commit cb7f3a5) was still leaving PLACEHOLDER rows in PG — but
manual UPDATE on the same row worked first try. Adding observability:

1. night-builder.js — split execSync return into 'last whitespace-
   delimited token' so we handle psql's 'INSERT 0 1\n<id>' output
   shape (RETURNING id arrives AFTER the row-count tag in some psql
   versions; .trim() alone wasn't enough). Also added try/catch around
   the UPDATE call with explicit log lines so the next failure tells
   us exactly which branch ran.

2. scripts/placeholder-watchdog.sh — independent self-healer. Loops
   every 30s, runs the idempotent UPDATE. Catches any stragglers from
   worker code bugs OR new code paths that miss the URL-patch step.
   Logs to /tmp/placeholder-watchdog.log. Survives until pkill -f
   placeholder-watchdog.sh.

Files touched

Diff

commit 2ea2f75dde4ee126151cc68af32d9b00e4ac1126
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 20 07:43:38 2026 -0700

    night-builder: debug-log url-patch step + ship self-healing watchdog
    
    V2 fix (commit cb7f3a5) was still leaving PLACEHOLDER rows in PG — but
    manual UPDATE on the same row worked first try. Adding observability:
    
    1. night-builder.js — split execSync return into 'last whitespace-
       delimited token' so we handle psql's 'INSERT 0 1\n<id>' output
       shape (RETURNING id arrives AFTER the row-count tag in some psql
       versions; .trim() alone wasn't enough). Also added try/catch around
       the UPDATE call with explicit log lines so the next failure tells
       us exactly which branch ran.
    
    2. scripts/placeholder-watchdog.sh — independent self-healer. Loops
       every 30s, runs the idempotent UPDATE. Catches any stragglers from
       worker code bugs OR new code paths that miss the URL-patch step.
       Logs to /tmp/placeholder-watchdog.log. Survives until pkill -f
       placeholder-watchdog.sh.
---
 scripts/night-builder.js        | 14 ++++++++++----
 scripts/placeholder-watchdog.sh | 14 ++++++++++++++
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/scripts/night-builder.js b/scripts/night-builder.js
index 39f712b..533e877 100755
--- a/scripts/night-builder.js
+++ b/scripts/night-builder.js
@@ -819,11 +819,17 @@ function generateOne(track, cw) {
                       FROM spoon_all_designs WHERE id=${id}
                       RETURNING id;`;
                     try {
-                      const cwId = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "${insertSql.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf8' }).trim();
+                      const insertRaw = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "${insertSql.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { encoding: 'utf8' });
+                      const cwId = insertRaw.trim().split(/\s+/).pop();  // psql -At with RETURNING + INSERT 0 1 → take LAST token
                       if (cwId && /^\d+$/.test(cwId)) {
-                        // Immediately patch the image_url so the row passes
-                        // designHasImage's /by-id/\d+ regex on /designs.
-                        execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${cwId}' WHERE id=${cwId};"`, { encoding: 'utf8' });
+                        try {
+                          const updOut = execSync(`/opt/homebrew/opt/postgresql@14/bin/psql dw_unified -At -c "UPDATE spoon_all_designs SET image_url='/designs/img/by-id/${cwId}' WHERE id=${cwId};"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
+                          logLine(`    cw url-patch id=${cwId} → ${updOut || '(empty)'}`);
+                        } catch (uErr) {
+                          logLine(`    cw url-patch FAIL id=${cwId}: ${(uErr.message || '').slice(0, 200)}`);
+                        }
+                      } else {
+                        logLine(`    cw NO-ID raw=${JSON.stringify(insertRaw).slice(0, 300)}`);
                       }
                       logLine(`    cw child id=${cwId} (${v.slug}) of parent ${id}`);
                     } catch (e2) {
diff --git a/scripts/placeholder-watchdog.sh b/scripts/placeholder-watchdog.sh
new file mode 100755
index 0000000..d38c6ed
--- /dev/null
+++ b/scripts/placeholder-watchdog.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# Self-healing PLACEHOLDER row patcher. Runs every 30s, idempotently UPDATEs
+# any spoon_all_designs row whose image_url is /designs/img/by-id/PLACEHOLDER
+# to use its actual id. Belt-and-suspenders backstop for the night-builder
+# colorway-INSERT path (2026-05-20).
+set -u
+LOG=/tmp/placeholder-watchdog.log
+while :; do
+  fixed=$(psql dw_unified -tAc "UPDATE spoon_all_designs SET image_url='/designs/img/by-id/'||id::text WHERE image_url='/designs/img/by-id/PLACEHOLDER' RETURNING id;" 2>/dev/null | wc -l | tr -d ' ')
+  if [ "$fixed" -gt 0 ]; then
+    echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] patched $fixed PLACEHOLDER rows" >> "$LOG"
+  fi
+  sleep 30
+done

← cb7f3a5 night-builder: split CTE → 2 separate psql calls (PG data-mo  ·  back to Wallco Ai  ·  night-builder: take FIRST line of psql RETURNING output, not 1e6e13a →