[object Object]

← back to Dw Marketing Reels

publish-ig.mjs: refuse non-postable accounts at the resolver with a clear reason (TK-10447)

c396f4bf51447bd6b6c15e89646aad48ee72d3a8 · 2026-08-25 14:45:11 -0700 · Steve Abrams

The roster demotion protected the console UI but NOT the publish script: resolveAccountUid
still resolved grasscloth's ig_user_id (present but unreachable), so a cron/retry/manual
--account ig-grassclothwallpaper would march into the Graph post and fail with an opaque
'Object does not exist'. Now the resolver carries status and the publish path hard-fails
BEFORE the media/Graph calls on any status != ready, printing the real reason.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit c396f4bf51447bd6b6c15e89646aad48ee72d3a8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Aug 25 14:45:11 2026 -0700

    publish-ig.mjs: refuse non-postable accounts at the resolver with a clear reason (TK-10447)
    
    The roster demotion protected the console UI but NOT the publish script: resolveAccountUid
    still resolved grasscloth's ig_user_id (present but unreachable), so a cron/retry/manual
    --account ig-grassclothwallpaper would march into the Graph post and fail with an opaque
    'Object does not exist'. Now the resolver carries status and the publish path hard-fails
    BEFORE the media/Graph calls on any status != ready, printing the real reason.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 scripts/publish-ig.mjs | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/scripts/publish-ig.mjs b/scripts/publish-ig.mjs
index aa8fffa..e2c21fe 100644
--- a/scripts/publish-ig.mjs
+++ b/scripts/publish-ig.mjs
@@ -40,11 +40,11 @@ function resolveAccountUid(sel) {
   const load = f => { try { return JSON.parse(readFileSync(join(ROOT, 'data', f), 'utf8')); } catch { return null; } };
   const managed = load('ig-accounts.json') || [];
   const m = managed.find(a => String(a.id).toLowerCase() === key || String(a.handle || '').toLowerCase() === key);
-  if (m && m.ig_user_id) return { uid: m.ig_user_id, label: m.label || m.handle };
+  if (m && m.ig_user_id) return { uid: m.ig_user_id, label: m.label || m.handle, status: m.status, note: m.note };
   const canon = (load('dw-canonical-accounts.json') || {}).accounts || [];
   const c = canon.find(a => String(a.handle || '').toLowerCase() === key
     || 'ig-' + String(a.handle || '').toLowerCase().replace(/[^a-z0-9-]+/g, '-') === key);
-  if (c && c.ig_user_id) return { uid: c.ig_user_id, label: c.name || c.handle };
+  if (c && c.ig_user_id) return { uid: c.ig_user_id, label: c.name || c.handle, status: 'ready' };
   return null;
 }
 
@@ -78,7 +78,14 @@ if (!tok) die('IG_ACCESS_TOKEN not found (env or Norma agent .env)');
 const selected = argAccount || reel.igAccount || '';
 const acct = resolveAccountUid(selected);
 let acctLabel = 'env default (main DW)';
-if (acct) { uid = acct.uid; acctLabel = acct.label; }
+if (acct) {
+  // Refuse non-postable accounts BEFORE the media/Graph calls, with the real reason —
+  // otherwise the ig_user_id resolves but the Graph post fails with an opaque
+  // "Object does not exist" (e.g. @grassclothwallpaper, unlinked from the Page; TK-10447).
+  if (acct.status && acct.status !== 'ready')
+    die(`account "${selected}" is not postable (status: ${acct.status}). ${acct.note || 'Re-link/create it in Meta Business Manager, then re-run scripts/build-ig-accounts.js.'}`);
+  uid = acct.uid; acctLabel = acct.label;
+}
 else if (selected) die(`account not resolvable to an ig_user_id: ${selected}`);
 if (!uid) die('IG_USER_ID not found (no account selected and no env IG_USER_ID)');
 const mediaUrl = `${PUBLIC_BASE}/reels/${encodeURIComponent(reel.file)}`;

← 0537a8a auto-data-snapshot: 2026-08-25T14:34:54 (1 data files) — dat  ·  back to Dw Marketing Reels  ·  auto-data-snapshot: 2026-08-25T17:39:55 (1 data files) — dat 9b8ae10 →