[object Object]

← back to Designer Wallcoverings

Harden Tres Tintas room generator: per-request fetch timeout + 5-min wall-clock poll cap (fixes hung-socket batch wedge)

c222b30cc1acf7f76f19392b32c5092c8972b27d · 2026-06-23 11:36:00 -0700 · Steve Abrams

Files touched

Diff

commit c222b30cc1acf7f76f19392b32c5092c8972b27d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 23 11:36:00 2026 -0700

    Harden Tres Tintas room generator: per-request fetch timeout + 5-min wall-clock poll cap (fixes hung-socket batch wedge)
---
 shopify/scripts/tres-tintas-room-settings.js | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/shopify/scripts/tres-tintas-room-settings.js b/shopify/scripts/tres-tintas-room-settings.js
index a13b6142..f65889ee 100644
--- a/shopify/scripts/tres-tintas-room-settings.js
+++ b/shopify/scripts/tres-tintas-room-settings.js
@@ -54,8 +54,16 @@ fs.mkdirSync(BACKUP_DIR, { recursive: true });
 const done = new Set(fs.existsSync(LEDGER) ? fs.readFileSync(LEDGER, 'utf8').split('\n').filter(Boolean) : []);
 
 const sleep = (ms) => new Promise(r => setTimeout(r, ms));
-async function fetchRetry(url, opts, tries = 3) {
-  let e; for (let i = 0; i < tries; i++) { try { return await fetch(url, opts); } catch (err) { e = err; await sleep(800 * (i + 1)); } } throw e;
+// Per-request timeout so a half-open socket can never wedge the batch (the
+// 2026-06-23 25-min hang). Default 60s; uploads/large posts get more headroom.
+async function fetchRetry(url, opts, tries = 3, timeoutMs = 60000) {
+  let e;
+  for (let i = 0; i < tries; i++) {
+    try {
+      return await fetch(url, { ...opts, signal: AbortSignal.timeout(timeoutMs) });
+    } catch (err) { e = err; await sleep(800 * (i + 1)); }
+  }
+  throw e;
 }
 async function gql(query, variables) {
   const r = await fetch(`https://${STORE}/admin/api/${API}/graphql.json`, { method: 'POST', headers: H, body: JSON.stringify({ query, variables }) });
@@ -75,10 +83,12 @@ async function replicateInpaint(initPath, maskPath, outPath) {
   let j = await (await fetchRetry('https://api.replicate.com/v1/predictions', { method: 'POST', headers: { Authorization: `Bearer ${RTOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(body) })).json();
   if (j.error) throw new Error('replicate create: ' + JSON.stringify(j.error));
   const url = j.urls.get;
+  const deadline = Date.now() + 5 * 60 * 1000; // hard 5-min wall-clock cap per prediction
   for (let i = 0; i < 90; i++) {
+    if (Date.now() > deadline) throw new Error('replicate wall-clock timeout');
     await sleep(2000);
     const pr = await (await fetchRetry(url, { headers: { Authorization: `Bearer ${RTOKEN}` } })).json();
-    if (pr.status === 'succeeded') { const out = Array.isArray(pr.output) ? pr.output[0] : pr.output; const img = await (await fetchRetry(out, {})).arrayBuffer(); fs.writeFileSync(outPath, Buffer.from(img)); return; }
+    if (pr.status === 'succeeded') { const out = Array.isArray(pr.output) ? pr.output[0] : pr.output; const img = await (await fetchRetry(out, {}, 3, 120000)).arrayBuffer(); fs.writeFileSync(outPath, Buffer.from(img)); return; }
     if (pr.status === 'failed' || pr.status === 'canceled') throw new Error('replicate ' + pr.status + ': ' + JSON.stringify(pr.error));
   }
   throw new Error('replicate poll timeout');
@@ -87,7 +97,7 @@ async function replicateInpaint(initPath, maskPath, outPath) {
 async function uploadImage(productId, filePath, alt) {
   const b64 = fs.readFileSync(filePath).toString('base64');
   const r = await fetchRetry(`https://${STORE}/admin/api/${API}/products/${productId}/images.json`, {
-    method: 'POST', headers: H, body: JSON.stringify({ image: { attachment: b64, filename: `${productId}-room-${ROOM}.png`, alt } }) });
+    method: 'POST', headers: H, body: JSON.stringify({ image: { attachment: b64, filename: `${productId}-room-${ROOM}.png`, alt } }) }, 3, 120000);
   const j = await r.json();
   if (!j.image) throw new Error('shopify upload: ' + JSON.stringify(j.errors || j));
   return j.image.id;

← afe6ecc0 auto-save: 2026-06-23T11:21:18 (3 files) — shopify/scripts/c  ·  back to Designer Wallcoverings  ·  Harden Tres Tintas spec recrawl: per-request fetch timeout ( 6638fe05 →