[object Object]

← back to Designer Wallcoverings

Fix silent-throttle bug in mfr push: detect top-level errors/null-data/429, retry w/ backoff, adaptive pacing, honest accounting + failed.csv

0c1bf6d1cf22bfbf28d528222a4ee5a6036cc6f3 · 2026-07-12 10:06:09 -0700 · Steve

Files touched

Diff

commit 0c1bf6d1cf22bfbf28d528222a4ee5a6036cc6f3
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Jul 12 10:06:09 2026 -0700

    Fix silent-throttle bug in mfr push: detect top-level errors/null-data/429, retry w/ backoff, adaptive pacing, honest accounting + failed.csv
---
 scripts/houston-mfr-fix.js | 38 ++++++++++++++++++++++++++------------
 1 file changed, 26 insertions(+), 12 deletions(-)

diff --git a/scripts/houston-mfr-fix.js b/scripts/houston-mfr-fix.js
index 804aa159..b6e791f6 100644
--- a/scripts/houston-mfr-fix.js
+++ b/scripts/houston-mfr-fix.js
@@ -116,25 +116,39 @@ async function main() {
 
   if (!EXECUTE) { console.log(`\n(dry run — no Shopify writes. re-run with --execute to push)\n`); await pool.end(); return; }
 
-  // 2. GATED execution path
-  let ok = 0, err = 0;
+  // 2. GATED execution path — throttle-aware + retry + HONEST accounting
+  const sleep = ms => new Promise(r => setTimeout(r, ms));
+  let ok = 0, err = 0; const failed = [];
   for (const m of manifest) {
     const gid = m.shopify_id.startsWith('gid://') ? m.shopify_id : `gid://shopify/Product/${m.shopify_id}`;
-    const body = { query: `mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){userErrors{field message}}}`,
+    const body = { query: `mutation($mf:[MetafieldsSetInput!]!){metafieldsSet(metafields:$mf){metafields{id} userErrors{field message}}}`,
       variables: { mf: [
         { ownerId: gid, namespace: 'custom', key: 'manufacturer_sku', value: m.new, type: 'single_line_text_field' },
         { ownerId: gid, namespace: 'dwc',    key: 'manufacturer_sku', value: m.new, type: 'single_line_text_field' } ] } };
-    try {
-      const res = await fetch(`https://${SHOP}/admin/api/${API}/graphql.json`,
-        { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
-      const j = await res.json();
-      const ue = j?.data?.metafieldsSet?.userErrors || [];
-      if (ue.length) { err++; console.error(`  ERR ${m.dw_sku}: ${JSON.stringify(ue)}`); }
-      else { ok++; if (ok % 250 === 0) console.log(`  ...${ok} pushed`); }
-    } catch (e) { err++; console.error(`  ERR ${m.dw_sku}: ${e.message}`); }
-    await new Promise(r => setTimeout(r, 60)); // gentle throttle
+    let wrote = false, lastErr = '';
+    for (let attempt = 1; attempt <= 6 && !wrote; attempt++) {
+      try {
+        const res = await fetch(`https://${SHOP}/admin/api/${API}/graphql.json`,
+          { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
+        const j = await res.json();
+        // THROTTLED / transport failure → top-level errors or null data: back off + retry (was silently counted OK before)
+        if (res.status === 429 || j.errors || !j.data || !j.data.metafieldsSet) {
+          lastErr = `throttle/${res.status}:${JSON.stringify(j.errors||'null-data')}`; await sleep(1500 * attempt); continue;
+        }
+        const ue = j.data.metafieldsSet.userErrors || [];
+        if (ue.length) { lastErr = JSON.stringify(ue); break; }              // real userError → don't retry
+        if (!j.data.metafieldsSet.metafields?.length) { lastErr = 'no-metafields-returned'; await sleep(1500*attempt); continue; }
+        wrote = true;
+        const avail = j.extensions?.cost?.throttleStatus?.currentlyAvailable ?? 1000; // adaptive pacing off the real bucket
+        await sleep(avail < 300 ? 800 : 220);
+      } catch (e) { lastErr = e.message; await sleep(1500 * attempt); }
+    }
+    if (wrote) { ok++; if (ok % 250 === 0) console.log(`  ...${ok} pushed`); }
+    else { err++; failed.push(m.dw_sku); console.error(`  ERR ${m.dw_sku}: ${lastErr}`); }
   }
   console.log(`\nDONE — pushed ${ok}, errors ${err}. Also re-sync the local mirror.`);
+  if (failed.length) { fs.writeFileSync(path.join(outDir, 'mfr-fix-failed.csv'), 'dw_sku\n' + failed.join('\n'));
+    console.log(`  ${failed.length} failures → reports/mfr-houston/mfr-fix-failed.csv`); }
   await pool.end();
 }
 main().catch(e => { console.error(e); process.exit(1); });

← 60525f46 PR quote-only tagging tool + manifest (DTD verdict C: quote-  ·  back to Designer Wallcoverings  ·  auto-save: 2026-07-12T10:17:36 (5 files) — pending-approval/ d1ffa01d →