[object Object]

← back to Designer Wallcoverings

drain self-heal round 2: missing playwright browsers treated as env failure (refund attempt, auto npx playwright install in ensure-import-app, resurrect pattern widened); app restarted onto playwright 1.57 binaries

615561a2fe53e0d72a5429d4d02573ec87990083 · 2026-07-13 08:35:31 -0700 · Steve

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files touched

Diff

commit 615561a2fe53e0d72a5429d4d02573ec87990083
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 13 08:35:31 2026 -0700

    drain self-heal round 2: missing playwright browsers treated as env failure (refund attempt, auto npx playwright install in ensure-import-app, resurrect pattern widened); app restarted onto playwright 1.57 binaries
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 .../ImportNewSkufromURL/scripts/ensure-import-app.sh        |  9 +++++++++
 .../ImportNewSkufromURL/scripts/import-queue-runner.js      | 13 ++++++++-----
 .../ImportNewSkufromURL/scripts/resurrect-infra-failed.js   |  2 +-
 3 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/DW-Programming/ImportNewSkufromURL/scripts/ensure-import-app.sh b/DW-Programming/ImportNewSkufromURL/scripts/ensure-import-app.sh
index 68fb0d0d..3a675098 100755
--- a/DW-Programming/ImportNewSkufromURL/scripts/ensure-import-app.sh
+++ b/DW-Programming/ImportNewSkufromURL/scripts/ensure-import-app.sh
@@ -19,6 +19,15 @@ alive() {
   [ -n "$code" ] && [ "$code" != "000" ]
 }
 
+# ENV HEAL: scrapes die with "Browser launch failed: Executable doesn't exist"
+# when a playwright update outpaces its downloaded browsers (bit us 2026-07-13,
+# burning items as failed while the app itself was healthy). Idempotent + quick
+# when binaries already exist.
+if ! ls "$HOME/Library/Caches/ms-playwright"/chromium_headless_shell-*/chrome-headless-shell-mac-*/chrome-headless-shell >/dev/null 2>&1; then
+  echo "[ensure-import-app] playwright headless-shell missing — installing"
+  (cd "$APP_DIR" && npx playwright install chromium chromium-headless-shell >/dev/null 2>&1)
+fi
+
 alive && exit 0
 
 echo "[ensure-import-app] :9830 dead — attempting pm2 heal $(date)"
diff --git a/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js b/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js
index 9dc799c7..c0d38ac9 100644
--- a/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js
+++ b/DW-Programming/ImportNewSkufromURL/scripts/import-queue-runner.js
@@ -103,7 +103,8 @@ const sleep = (ms) => new Promise(r => setTimeout(r, ms));
 // shared ensure-import-app.sh (pm2 restart/start + poll); if it can't come back,
 // exit 69 (EX_UNAVAILABLE) so the drain driver sleeps and retries the whole pass.
 const { execFileSync } = require('child_process');
-const BACKEND_DOWN_RE = /ECONNREFUSED|ECONNRESET|socket hang up/i;
+const BACKEND_DOWN_RE = /ECONNREFUSED|ECONNRESET|socket hang up|Browser launch failed|Executable doesn't exist|playwright install/i;
+let envFailStreak = 0; // consecutive environment failures — bail at 3 so we don't spin forever
 function healBackend() {
   try {
     execFileSync('/bin/sh', [require('path').join(__dirname, 'ensure-import-app.sh')], { stdio: 'inherit', timeout: 180000 });
@@ -233,15 +234,17 @@ if (!QUEUE || !fs.existsSync(QUEUE)) { console.error('usage: import-queue-runner
           // driver retries the pass later instead of grinding the queue to failed.
           if (BACKEND_DOWN_RE.test(note)) {
             it.attempts--;
-            it.note = note + ' (backend down — attempt refunded)';
-            console.log(`  ⚕  ${it.sku}: ${note} — backend down, healing (attempt not charged)`);
+            it.note = note.slice(0, 200) + ' (env failure — attempt refunded)';
+            console.log(`  ⚕  ${it.sku}: env failure — healing (attempt not charged)`);
             save(items);
-            if (!healBackend()) {
-              console.log('[import-queue] backend unhealable — exiting 69 (EX_UNAVAILABLE)');
+            envFailStreak++;
+            if (envFailStreak >= 3 || !healBackend()) {
+              console.log('[import-queue] environment unhealable this pass — exiting 69 (EX_UNAVAILABLE)');
               process.exit(69);
             }
             continue; // same pass resumes with the next pending item
           }
+          envFailStreak = 0;
           // TRANSIENT one-off drops (e.g. request timeout mid-recompile) must NOT
           // permanently fail on one shot — leave pending.
           const transient = /timeout/i.test(note);
diff --git a/DW-Programming/ImportNewSkufromURL/scripts/resurrect-infra-failed.js b/DW-Programming/ImportNewSkufromURL/scripts/resurrect-infra-failed.js
index 551a7bdb..9be65ff6 100644
--- a/DW-Programming/ImportNewSkufromURL/scripts/resurrect-infra-failed.js
+++ b/DW-Programming/ImportNewSkufromURL/scripts/resurrect-infra-failed.js
@@ -14,7 +14,7 @@ const fs = require('fs');
 const path = require('path');
 
 const DIR = process.argv[2] || 'data/scraper-audit/reconcile';
-const INFRA_RE = /ECONNREFUSED|ECONNRESET|socket hang up|backend down|timeout/i;
+const INFRA_RE = /ECONNREFUSED|ECONNRESET|socket hang up|backend down|env failure|timeout|Browser launch failed|Executable doesn't exist|playwright install/i;
 
 let total = 0;
 for (const f of fs.readdirSync(DIR).filter(f => f.endsWith('-import-queue.jsonl'))) {

← 84bb022a import drain self-heals: backend-down never burns item attem  ·  back to Designer Wallcoverings  ·  chore: session-close quality gate — hoist sleep/isSample to 91304e05 →