← back to Dw Marketing Reels
TK-10395: surface Norma's real IG-publish error instead of a bare 'error'
50543e2ce03e6d993420ab8133d391913001cdff · 2026-08-10 07:55:54 -0700 · Steve Abrams
- !res.ok now includes body.error/note/message (was dropped) so the armed nightly run logs the actual cause
- guard the 200-OK-with-internal-failure path so a skill-level error isn't mislabeled 'posted'
- root cause is NOT a dead token (meta-token-canary: meta-ig-norma HEALTHY, expires=never) — this makes the next run reveal the true Norma-side error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M scripts/publish-social.mjs
Diff
commit 50543e2ce03e6d993420ab8133d391913001cdff
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 10 07:55:54 2026 -0700
TK-10395: surface Norma's real IG-publish error instead of a bare 'error'
- !res.ok now includes body.error/note/message (was dropped) so the armed nightly run logs the actual cause
- guard the 200-OK-with-internal-failure path so a skill-level error isn't mislabeled 'posted'
- root cause is NOT a dead token (meta-token-canary: meta-ig-norma HEALTHY, expires=never) — this makes the next run reveal the true Norma-side error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/publish-social.mjs | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/scripts/publish-social.mjs b/scripts/publish-social.mjs
index bef2bc4..f8c28a3 100644
--- a/scripts/publish-social.mjs
+++ b/scripts/publish-social.mjs
@@ -75,12 +75,24 @@ async function postInstagram(reel) {
}), 90000); // IG reel publish is async (container→poll→publish, ~30-60s); the 12s default timed out MID-POST on the first live fire (2026-07-27, post still landed).
const txt = await res.text().catch(() => '');
let body = {}; try { body = JSON.parse(txt); } catch {}
- if (!res.ok) return { status: res.status === 401 ? 'pending-creds' : 'error', account: acc.id, http: res.status, at: now() };
+ if (!res.ok) {
+ // Surface Norma's actual error, not a bare 'error'. The body was already
+ // parsed above; dropping it hid the real cause on the armed nightly run
+ // (TK-10395: token verified HEALTHY, so the failure is Norma-side and its
+ // message is the only lead). Fall back to raw text, then the HTTP code.
+ const note = (body && (body.error || body.note || body.message)) || (txt ? txt.slice(0, 300) : `HTTP ${res.status}`);
+ return { status: res.status === 401 ? 'pending-creds' : 'error', account: acc.id, http: res.status, note, at: now() };
+ }
// Norma wraps the skill output as { success, skill, result } — the real status/ids
// live on .result, NOT the envelope. Reading the envelope reported a false 'posted'
// for a no-creds account (contrarian-caught). Unwrap, then honor the skill's verdict.
const r = body.result || body;
if (r.status === 'no-creds-for-account') return { status: 'no-creds-for-account', account: acc.id, note: r.note, at: now() };
+ // A 200 envelope can still carry a skill-level failure. Don't mislabel that as
+ // 'posted' — surface it as an error with Norma's message so the nightly log is honest.
+ if (body.success === false || r.status === 'error' || r.status === 'failed' || r.error) {
+ return { status: 'error', account: acc.id, http: res.status, note: r.error || r.note || body.error || `skill returned status "${r.status}"`, at: now() };
+ }
// r.simulated is an explicit boolean from Norma's reel skill — read it directly.
// (The old /simulat/i text-regex falsely matched `"simulated":false`, mislabeling
// a real live post as 'simulated' — contrarian-adjacent bug, fixed with the unwrap.)
← db2c644 auto-data-snapshot: 2026-08-10T07:21:36 (3 data files) — dat
·
back to Dw Marketing Reels
·
TK-10395: add read-only publish-status monitor (catches 2207 d52056c →