[object Object]

← back to Wallco Ai

fix(/api/room): retry upstream once on fetch failed / transient errors

dbc500bd298eb7e581c37f0666d4d8f0fa13e232 · 2026-05-12 14:53:29 -0700 · SteveStudio2

The room-renderer service at 45.61.58.125:3075 drops 5-15% of requests
with {ok:false,error:'fetch failed'} or transient TLS hiccups. Surfaced
in the 27-render matrix where 5/27 cells failed cold and ALL FIVE
recovered on a single retry — meaning the failures were transient
upstream-side, not pattern-side.

Wraps the existing fetch in a 2-try loop with 800ms backoff. Returns
the last error in the response body if both attempts fail. Keeps the
b64 file read outside the loop (no double disk hit). No timeout
change — node fetch defaults already cover the upstream's ~9s p50.

Verified by re-running the 5 previously-failed matrix cells against
the patched server: 5/5 succeeded. Contact sheet now 27/27.

Files touched

Diff

commit dbc500bd298eb7e581c37f0666d4d8f0fa13e232
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Tue May 12 14:53:29 2026 -0700

    fix(/api/room): retry upstream once on fetch failed / transient errors
    
    The room-renderer service at 45.61.58.125:3075 drops 5-15% of requests
    with {ok:false,error:'fetch failed'} or transient TLS hiccups. Surfaced
    in the 27-render matrix where 5/27 cells failed cold and ALL FIVE
    recovered on a single retry — meaning the failures were transient
    upstream-side, not pattern-side.
    
    Wraps the existing fetch in a 2-try loop with 800ms backoff. Returns
    the last error in the response body if both attempts fail. Keeps the
    b64 file read outside the loop (no double disk hit). No timeout
    change — node fetch defaults already cover the upstream's ~9s p50.
    
    Verified by re-running the 5 previously-failed matrix cells against
    the patched server: 5/5 succeeded. Contact sheet now 27/27.
---
 server.js | 36 +++++++++++++++++++++++++-----------
 1 file changed, 25 insertions(+), 11 deletions(-)

diff --git a/server.js b/server.js
index df5fc00..9db5df3 100644
--- a/server.js
+++ b/server.js
@@ -6927,17 +6927,31 @@ app.post('/api/room', async (req, res) => {
   }
   try {
     const b64 = fs.readFileSync(file).toString('base64');
-    const upstream = await fetch(ROOM_API, {
-      method: 'POST',
-      headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify({
-        patternBase64: b64, roomType, angle,
-        cameraDistance, patternWidth, patternHeight
-      })
-    });
-    const j = await upstream.json();
-    if (!j || j.success === false || !j.image) {
-      return res.status(502).json({ ok: false, error: j && j.error || 'upstream error', upstream: j });
+    // Upstream renderer (45.61.58.125:3075) drops ~5-15% of requests with
+    // {ok:false,error:'fetch failed'} or transient TLS hiccups. Retry once
+    // with 800ms backoff before giving up — caught in the 27-render matrix
+    // 2026-05-12 where 5/27 cells failed and would have succeeded on retry.
+    let j = null, lastErr = null;
+    for (let attempt = 1; attempt <= 2; attempt++) {
+      try {
+        const upstream = await fetch(ROOM_API, {
+          method: 'POST',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({
+            patternBase64: b64, roomType, angle,
+            cameraDistance, patternWidth, patternHeight
+          })
+        });
+        const candidate = await upstream.json();
+        if (candidate && candidate.success !== false && candidate.image) { j = candidate; break; }
+        lastErr = (candidate && candidate.error) || 'upstream returned no image';
+      } catch (e) {
+        lastErr = e.message;
+      }
+      if (attempt < 2) await new Promise(r => setTimeout(r, 800));
+    }
+    if (!j) {
+      return res.status(502).json({ ok: false, error: lastErr || 'upstream error', retried: true });
     }
     // upstream returns { success, image, ... } — `image` is base64 or data-URL
     const imgB64 = j.image.replace(/^data:image\/\w+;base64,/, '');

← 48baf5a wallco.ai · audit log Phase 1 — capture all user + admin act  ·  back to Wallco Ai  ·  tools: add scripts/seam_check.py — quantify pattern seamless 7901449 →