From 2e8bd11a3a5dda035c3ac18c45518bb5fe86b48f Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 1 Sep 2026 16:17:07 -0400 Subject: [PATCH] instagram: say why a publish failed, and stop retrying a closed door Meta blocked publishing on @menuband.app on 2026-08-31. The lane retried every hour for 32 hours -- re-rendering, re-uploading 31 MB to Spaces, and being told "Fatal" -- because two things were missing. `call()` in ig.mjs threw away the half of Meta's error that means something. A reel that will not publish reports `Fatal (code -1/2207032)`, while the reason a person can act on rides in `error_user_title` / `error_user_msg`. Those are surfaced now, along with `fbtrace_id` -- the first thing Meta asks for about a failure whose message says nothing. Thirty-two failures all read as one indistinguishable word, and that cost a day of guessing. The clockwork counted nothing, so nothing escalated. It now keeps a tally of consecutive publish failures and holds the lane after three (MENUBAND_CLOCKWORK_BREAKER to change, `menuband-clockwork clear` to reset). A held run does no work, so it neither clears the tally nor inflates it -- only real attempts move the number, and a success resets it. A lane that cannot publish should go quiet, not get louder. Verified against an isolated state dir: counts 1/2/3, holds on the fourth without touching the checkout, leaves the tally at 3, and clear reports the prior count. Claude-Session: https://claude.ai/code/session_01X62vT9M1iQXjNTF1jMcpN5 --- toolchain/instagram/ig.mjs | 21 ++++++++++--- toolchain/instagram/menuband-clockwork.sh | 38 +++++++++++++++++++++-- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/toolchain/instagram/ig.mjs b/toolchain/instagram/ig.mjs index d758c596b9..348b4b3584 100644 --- a/toolchain/instagram/ig.mjs +++ b/toolchain/instagram/ig.mjs @@ -119,10 +119,23 @@ async function call(url, init = {}) { const body = await response.json().catch(() => ({})); if (!response.ok) { const err = body.error || body; - const msg = err.message - ? `${err.message}${err.code !== undefined ? ` (code ${err.code}${err.error_subcode ? `/${err.error_subcode}` : ""})` : ""}` - : JSON.stringify(err); - const e = new Error(`${response.status} · ${msg}`); + // `message` is often the useless half. A reel that will not publish reports + // `Fatal (code -1/2207032)` and nothing else, while the reason a person can + // act on — "we limit how often you can post", an account restriction, a + // media problem — rides in `error_user_title`/`error_user_msg`. Dropping + // those turned 32 consecutive @menuband publish failures into one + // indistinguishable word, and cost a day of guessing at the cause. + const parts = []; + if (err.message) parts.push(err.message); + else if (Object.keys(err).length) parts.push(JSON.stringify(err)); + if (err.code !== undefined) + parts.push(`(code ${err.code}${err.error_subcode ? `/${err.error_subcode}` : ""})`); + if (err.error_user_title) parts.push(`· ${err.error_user_title}`); + if (err.error_user_msg) parts.push(`· ${err.error_user_msg}`); + // The trace id is what Meta asks for first in any support thread, and it is + // the only handle on a failure whose message says nothing. + if (err.fbtrace_id) parts.push(`[fbtrace ${err.fbtrace_id}]`); + const e = new Error(`${response.status} · ${parts.join(" ")}`); e.meta = err; throw e; } diff --git a/toolchain/instagram/menuband-clockwork.sh b/toolchain/instagram/menuband-clockwork.sh index 139bb5c6c4..fe323a6b8e 100755 --- a/toolchain/instagram/menuband-clockwork.sh +++ b/toolchain/instagram/menuband-clockwork.sh @@ -3,6 +3,7 @@ # # menuband-clockwork publish # render + post the lane's next waltz # menuband-clockwork insights # pull Meta's figures onto the ledger +# menuband-clockwork clear # reset the consecutive-failure breaker # # A sibling of oskiewar-clockwork.sh with the same hard-won shape — node # resolved from installations (not a shell's PATH), the automation checkout @@ -48,13 +49,37 @@ if [ "$MODE" = "publish" ]; then fi fi +# --- the breaker: stop pushing on a door that is not opening --- +# Meta blocked publishing on this account on 2026-08-31 and the lane retried +# every hour for 32 hours, re-rendering and re-uploading 31 MB to Spaces each +# time, to be told "Fatal" and try again. Nothing counted, so nothing escalated. +# After this many consecutive failures the publish lane holds until a human +# clears it — a lane that cannot publish should go quiet, not get louder. +FAILS="$STATE/menuband-clockwork-fails" +BREAKER_TRIP="${MENUBAND_CLOCKWORK_BREAKER:-3}" +fail_count() { cat "$FAILS" 2>/dev/null || echo 0; } +TRIPPED=0 + +if [ "$MODE" = "clear" ]; then + WAS="$(fail_count)" # read before the delete, or it always reports zero + rm -f "$FAILS" + echo "menuband clockwork breaker cleared (was $WAS consecutive failures)" + exit 0 +fi + STAGE="starting" finish() { local code=$? [ "$MODE" = "publish" ] && rm -rf "$LOCK" - printf '{"format":"ac.menuband.clockwork","mode":"%s","startedAt":"%s","finishedAt":"%s","exitCode":%d,"stage":"%s","ok":%s}\n' \ + printf '{"format":"ac.menuband.clockwork","mode":"%s","startedAt":"%s","finishedAt":"%s","exitCode":%d,"stage":"%s","ok":%s,"consecutiveFailures":%s}\n' \ "$MODE" "$STARTED" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$code" "$STAGE" \ - "$([ $code -eq 0 ] && echo true || echo false)" > "$BEAT" + "$([ $code -eq 0 ] && echo true || echo false)" "$(fail_count)" > "$BEAT" + # A tripped run did no work, so it is neither a success to celebrate nor a + # fresh failure to count — leave the tally exactly where it was. + if [ "$TRIPPED" -eq 0 ] && [ "$MODE" = "publish" ]; then + if [ $code -eq 0 ]; then rm -f "$FAILS" + else echo $(( $(fail_count) + 1 )) > "$FAILS"; fi + fi if [ $code -ne 0 ]; then say "✗ FAILED at stage '$STAGE' (exit $code) · see $LOG" osascript -e "display notification \"menuband $MODE failed at $STAGE\" with title \"reel clockwork\"" 2>/dev/null || true @@ -63,6 +88,13 @@ finish() { } trap finish EXIT +if [ "$MODE" = "publish" ] && [ "$(fail_count)" -ge "$BREAKER_TRIP" ]; then + TRIPPED=1 + say "⛔ breaker open: $(fail_count) consecutive publish failures — holding." + say " Fix the cause, then: menuband-clockwork clear" + exit 0 +fi + # --- node, resolved from installations rather than from a shell's PATH --- STAGE="resolving node" NODE="" @@ -129,7 +161,7 @@ case "$MODE" in [ "$status" -eq 0 ] || exit "$status" ;; *) - say "unknown mode '$MODE' (want: publish | insights)"; exit 2 ;; + say "unknown mode '$MODE' (want: publish | insights | clear)"; exit 2 ;; esac # --- close the loop in git: the ledger is the record a human reads --- -- 2.51.2