← back to Rebel Walls Push
TK-10029: retry gql() on transient Shopify upstream errors (backoff)
c93997ddb048cef723495daa99a9bb0272e1352e · 2026-09-03 12:09:07 -0700 · Steve Abrams
A non-JSON 'upstream connect error' gateway blip was crashing long --apply
runs mid-flight (JSON.parse throw, no retry). Split into gqlOnce + a 6-attempt
exponential-backoff gql wrapper. Transport-only; rename set/hold logic unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M scripts/relabel-units.js
Diff
commit c93997ddb048cef723495daa99a9bb0272e1352e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 3 12:09:07 2026 -0700
TK-10029: retry gql() on transient Shopify upstream errors (backoff)
A non-JSON 'upstream connect error' gateway blip was crashing long --apply
runs mid-flight (JSON.parse throw, no retry). Split into gqlOnce + a 6-attempt
exponential-backoff gql wrapper. Transport-only; rename set/hold logic unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/relabel-units.js | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/scripts/relabel-units.js b/scripts/relabel-units.js
index b69caa6..80f6f05 100644
--- a/scripts/relabel-units.js
+++ b/scripts/relabel-units.js
@@ -65,7 +65,7 @@ function isWrongMuralLabel(value) {
return false;
}
-async function gql(query, vars) {
+async function gqlOnce(query, vars) {
return new Promise((resolve, reject) => {
const body = JSON.stringify({ query, variables: vars || {} });
const req = https.request({
@@ -75,7 +75,7 @@ async function gql(query, vars) {
}, res => {
let d = '';
res.on('data', c => d += c);
- res.on('end', () => { try { resolve(JSON.parse(d)); } catch(e) { reject(e); } });
+ res.on('end', () => { try { resolve(JSON.parse(d)); } catch(e) { reject(new Error(`non-JSON response (${res.statusCode}): ${String(d).slice(0,80)}`)); } });
});
req.on('error', reject);
req.write(body);
@@ -83,6 +83,24 @@ async function gql(query, vars) {
});
}
+// TK-10029: Shopify's gateway intermittently returns a non-JSON "upstream connect error"
+// (transient 502/503) that would otherwise crash a long apply run mid-flight. Retry transient
+// failures with exponential backoff; surface a permanent failure only after N attempts.
+async function gql(query, vars) {
+ let lastErr;
+ for (let attempt = 1; attempt <= 6; attempt++) {
+ try { return await gqlOnce(query, vars); }
+ catch (e) {
+ lastErr = e;
+ if (attempt === 6) break;
+ const backoff = Math.min(8000, 400 * 2 ** (attempt - 1));
+ console.log(` [gql retry ${attempt}/5] ${e.message} — waiting ${backoff}ms`);
+ await sleep(backoff);
+ }
+ }
+ throw lastErr;
+}
+
async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
async function fetchAll() {
← dd06ad8 TK-10029: durable scope-B rollback map (1220 rows) + generat
·
back to Rebel Walls Push
·
TK-10029: runnable Scope-B rollback (reverse 1220 relabels f 12856c3 →