← back to Marketing Command Center
MCC bluesky: fail loud if image can't embed — never silently post text-only
39fcdefaafa8b97bbc82d349ef3296816f78b0d7 · 2026-07-27 15:56:08 -0700 · Steve Abrams
postBluesky() previously swallowed any image fetch/uploadBlob failure and posted
text-only anyway (embed left undefined), which slips past the route-level text-only
guard (content.mediaUrl was truthy upstream) and violates Steve's 2026-07-17
'DW never posts text-only' rule — silently, on ok:true. Now aborts the post with a
loud error on any fetch/upload failure. Found by /contrarian gating TK-8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
M modules/channels/index.js
Diff
commit 39fcdefaafa8b97bbc82d349ef3296816f78b0d7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 27 15:56:08 2026 -0700
MCC bluesky: fail loud if image can't embed — never silently post text-only
postBluesky() previously swallowed any image fetch/uploadBlob failure and posted
text-only anyway (embed left undefined), which slips past the route-level text-only
guard (content.mediaUrl was truthy upstream) and violates Steve's 2026-07-17
'DW never posts text-only' rule — silently, on ok:true. Now aborts the post with a
loud error on any fetch/upload failure. Found by /contrarian gating TK-8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
modules/channels/index.js | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/modules/channels/index.js b/modules/channels/index.js
index ae46f7c..6c202ad 100644
--- a/modules/channels/index.js
+++ b/modules/channels/index.js
@@ -596,16 +596,25 @@ async function postBluesky(content) {
const jwt = sj.accessJwt, did = sj.did;
let embed;
if (content.mediaUrl) {
+ // HARD RULE (Steve 2026-07-17): DW never posts text-only. If a mediaUrl was
+ // supplied, the image MUST embed — if the fetch or uploadBlob fails, ABORT the
+ // post instead of silently falling back to text-only. The route-level text-only
+ // guard can't catch that fallback because content.mediaUrl was truthy upstream,
+ // so this is the only place it can be enforced. Fail LOUD, never post naked.
try {
const img = await fetch(content.mediaUrl);
+ if (!img.ok) return [{ ok: false, error: `Bluesky image fetch failed (${img.status}) — post aborted; DW never posts text-only` }];
const buf = Buffer.from(await img.arrayBuffer());
const ct = img.headers.get('content-type') || 'image/jpeg';
const up = await fetch(`${PDS}/com.atproto.repo.uploadBlob`, {
method: 'POST', headers: { 'content-type': ct, authorization: `Bearer ${jwt}` }, body: buf,
});
const uj = await up.json().catch(() => ({}));
- if (up.ok && uj.blob) embed = { $type: 'app.bsky.embed.images', images: [{ alt: (content.caption || '').slice(0, 280), image: uj.blob }] };
- } catch { /* post text-only if image fetch/upload fails */ }
+ if (!up.ok || !uj.blob) return [{ ok: false, error: `Bluesky image upload failed (${up.status}) — post aborted; DW never posts text-only` }];
+ embed = { $type: 'app.bsky.embed.images', images: [{ alt: (content.caption || '').slice(0, 280), image: uj.blob }] };
+ } catch (e) {
+ return [{ ok: false, error: `Bluesky image upload error: ${e.message} — post aborted; DW never posts text-only` }];
+ }
}
const text = (content.caption || '').slice(0, 300);
const record = { $type: 'app.bsky.feed.post', text, createdAt: new Date().toISOString() };
← f4429c8 auto-save: 2026-07-27T15:21:37 (1 files) — data/cc-warmup-se
·
back to Marketing Command Center
·
(newest)