[object Object]

← back to Designerwallcoverings

lib/shopify: probe SHOPIFY_FULL_ACCESS_TOKEN once, fall back to SHOPIFY_ADMIN_TOKEN on a definitive 401

cb3710a78531b36d0b153de9cc980445b35706e1 · 2026-09-24 16:02:08 -0700 · Steve Abrams

The preferred token (…2ea5) is dead, which broke all 88 importers. Only a 401 triggers
the fallback; network errors and other statuses keep the preferred token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SrEDNvrmHyv2QgB23Lm151

Files touched

Diff

commit cb3710a78531b36d0b153de9cc980445b35706e1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 24 16:02:08 2026 -0700

    lib/shopify: probe SHOPIFY_FULL_ACCESS_TOKEN once, fall back to SHOPIFY_ADMIN_TOKEN on a definitive 401
    
    The preferred token (…2ea5) is dead, which broke all 88 importers. Only a 401 triggers
    the fallback; network errors and other statuses keep the preferred token.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01SrEDNvrmHyv2QgB23Lm151
---
 scripts/lib/shopify.mjs | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/scripts/lib/shopify.mjs b/scripts/lib/shopify.mjs
index 15f1908..dddad4e 100644
--- a/scripts/lib/shopify.mjs
+++ b/scripts/lib/shopify.mjs
@@ -24,9 +24,20 @@ const _env = fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.env'
 // Prefer the full-scope token (write_inventory + all others); fall back to the narrow custom-app token.
 // TK-11055: onboarders that call inventoryItemUpdate need write_inventory scope, which the narrow
 // SHOPIFY_ADMIN_TOKEN (…7d19, 4 scopes) lacks. SHOPIFY_FULL_ACCESS_TOKEN has 139 scopes.
-export const TOKEN = (
-  _env.match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m) || _env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m) || []
-)[1]?.trim();
+// A dead preferred token (…2ea5 went 401 in Sep 2026) used to break every importer, so probe it
+// once: on a definitive 401 fall back to SHOPIFY_ADMIN_TOKEN (now the 139-scope "Full Access" app).
+// A network error or any other status keeps the preferred token so a blip can't silently swap it.
+const _full  = _env.match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m)?.[1]?.trim();
+const _admin = _env.match(/^SHOPIFY_ADMIN_TOKEN=(.+)$/m)?.[1]?.trim();
+async function _pickToken() {
+  if (!_full || !_admin) return _full || _admin;
+  try {
+    const r = await fetch(`https://${SHOP}/admin/api/${VER}/shop.json`, { headers: { 'X-Shopify-Access-Token': _full }, signal: AbortSignal.timeout(10000) });
+    if (r.status === 401) { console.error(`[lib/shopify] SHOPIFY_FULL_ACCESS_TOKEN (…${_full.slice(-4)}) returned 401 — using SHOPIFY_ADMIN_TOKEN (…${_admin.slice(-4)})`); return _admin; }
+  } catch { /* keep the preferred token */ }
+  return _full;
+}
+export const TOKEN = await _pickToken();
 if (!TOKEN) { console.error('no SHOPIFY_FULL_ACCESS_TOKEN or SHOPIFY_ADMIN_TOKEN in ~/Projects/secrets-manager/.env'); process.exit(1); }
 
 export const ENDPOINT = `https://${SHOP}/admin/api/${VER}`;

← 398290e night-pdp-verify: unknown expected price -> NOT-MEASURED, ke  ·  back to Designerwallcoverings  ·  auto-data-snapshot: 2026-09-24T17:57:06 (1 data files) — dat 7e676fe →