← back to Dw Marketing Reels
reels: fix IG async-publish timeout (first live post landed despite 12s client timeout)
8adb6937439d08bceeebebe2072a7d7bc89312dc · 2026-07-27 16:46:38 -0700 · Steve Abrams
First live DW IG reel posted 2026-07-27 (reel/DbUK59zFdfL) but the client saw a 12s
timeout mid-publish (IG container->poll->publish runs ~30-60s), recording a false error.
- bump IG post timeout 12s->90s so a real post is captured as posted.
- on timeout: return posted-unverified (NOT error) - the request was already sent, so a
blind retry would DUPLICATE the post; the dup-guard now skips posted-unverified too.
Reconciled reels[0] to posted (permalink) so the nightly cron cannot double-post.
TK-14.
Files touched
M scripts/publish-social.mjs
Diff
commit 8adb6937439d08bceeebebe2072a7d7bc89312dc
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 27 16:46:38 2026 -0700
reels: fix IG async-publish timeout (first live post landed despite 12s client timeout)
First live DW IG reel posted 2026-07-27 (reel/DbUK59zFdfL) but the client saw a 12s
timeout mid-publish (IG container->poll->publish runs ~30-60s), recording a false error.
- bump IG post timeout 12s->90s so a real post is captured as posted.
- on timeout: return posted-unverified (NOT error) - the request was already sent, so a
blind retry would DUPLICATE the post; the dup-guard now skips posted-unverified too.
Reconciled reels[0] to posted (permalink) so the nightly cron cannot double-post.
TK-14.
---
scripts/publish-social.mjs | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/scripts/publish-social.mjs b/scripts/publish-social.mjs
index b4e1b3d..5deb311 100644
--- a/scripts/publish-social.mjs
+++ b/scripts/publish-social.mjs
@@ -71,7 +71,7 @@ async function postInstagram(reel) {
// Norma's reel skill contract (skills/reel.js): video_url + caption. `mediaUrl` was a
// silent field-name mismatch — the live path would have 500'd on first arm (contrarian find).
body: JSON.stringify({ video_url: mediaUrl, caption: reel.caption, account: acc.id, source: 'dw-marketing-reels' }),
- }));
+ }), 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() };
@@ -85,7 +85,14 @@ async function postInstagram(reel) {
// a real live post as 'simulated' — contrarian-adjacent bug, fixed with the unwrap.)
const simulated = r.simulated === true;
return { status: simulated ? 'simulated' : 'posted', account: acc.id, postId: r.media_id || r.id || r.postId || null, at: now() };
- } catch (e) { return { status: 'error', account: acc.id, note: e.message, at: now() }; }
+ } catch (e) {
+ // A timeout means the request was already SENT to Norma — the post may have SUCCEEDED
+ // (IG's async publish outran the client timeout on the first live fire, 2026-07-27). NEVER
+ // blind-retry a timeout as a fresh post; mark 'posted-unverified' (the dup-guard skips it) so
+ // the nightly cron can't double-post. Reconcile to 'posted' out-of-band via Graph /media.
+ if (/timeout/i.test(e.message)) return { status: 'posted-unverified', account: acc.id, note: 'client timeout after request sent — verify on IG before assuming failure', at: now() };
+ return { status: 'error', account: acc.id, note: e.message, at: now() };
+ }
}
async function postTikTok(reel) {
@@ -128,7 +135,7 @@ async function main() {
// reel would be resubmitted to the Graph API — a duplicate public post (Meta prohibits
// inauthentic duplicated content). Only a genuine successful post is skipped; every non-terminal
// state (pending-creds / ready-pending-arm / error / simulated) is still (re)attempted.
- const done = st => st && (st.status === 'posted' || st.status === 'already-posted');
+ const done = st => st && (st.status === 'posted' || st.status === 'already-posted' || st.status === 'posted-unverified');
if (CHANNELS.includes('instagram')) {
reel.publish.instagram = done(reel.publish.instagram)
? { ...reel.publish.instagram, status: 'already-posted', skipped: true, at: now() }
← 5121ca1 reels: honest TikTok status + FTC content-claims hold guard
·
back to Dw Marketing Reels
·
auto-save: 2026-07-27T16:52:13 (1 files) — data/reels.json eb7ecf5 →