Skip to content

Retry a failed render instead of returning the failure forever - #285

Merged
ralyodio merged 1 commit into
masterfrom
retry-failed-renders
Sep 24, 2026
Merged

ralyodio merged 1 commit into
masterfrom
retry-failed-renders

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

A failed render was a permanent verdict on a design.

Dedupe is keyed by a hash of the design, and the reuse branch handed back whatever row it found — including a failed one. So the first failure stuck forever: re-saving the campaign produced the same hash, found the same failed row, and queued nothing.

The render card's own advice — "Editing the campaign queues a fresh attempt" — could not work, because an unedited campaign hashes identically. I wrote that line believing it.

How it surfaced

Immediately after the animated-banner frame-pattern bug. Three good pre-rolls had been failed by a broken GIF encode; with that fixed and deployed, the same campaign still wouldn't render:

have  1/1 https://coinpayportal.com/ — job cda87f50… (failed)
Done. queued=0 already-had=1 skipped=0 failed=0

Queued nothing. The fix was live and unreachable.

The change

A failed row is requeued and re-enqueued, capped at three attempts.

The cap matters: the failure may be deterministic — a snapshot this renderer simply cannot draw — and the row is keyed by design rather than by attempt, so without a cap every save of an unrenderable campaign would queue the same doomed work again, forever.

Verification

Two tests: a failed job below the cap is requeued with its error_code cleared; at the cap it is returned as-is with no write. 2565 passed / 1 failed repo-wide — the pre-existing tracker-geo failure. Typechecks clean.

After this deploys I'll re-queue the canary and confirm three GIFs actually land, then look at them before reporting the feature as working.

Dedupe is keyed by a hash of the design, and the reuse branch handed back
whatever row it found — including a failed one. So the first failure became a
permanent verdict on that copy: re-saving the campaign produced the same hash,
found the same failed row, and queued nothing. The render card's advice to edit
and save to try again was advice that could not work, because an unedited
campaign hashes identically.

This surfaced immediately after the animated banner bug: three good pre-rolls
had been failed by a broken GIF encode, and with that fixed and deployed the
campaigns still would not render, because the failed rows were being handed
straight back.

A failed row is now requeued and re-enqueued, capped at three attempts. The cap
matters because the failure may be deterministic — a snapshot this renderer
cannot draw — and the row is keyed by design rather than by attempt, so without
one every save of an unrenderable campaign would queue the same doomed work
again.

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

Copy link
Copy Markdown

ThreatCrush Security Scan

48 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 31 | LOW: 15

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:48
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM js-dynamic-code-execution lib/crawl-limits.ts:67
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:99
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:14
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:24
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:20
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:24
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:25
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:26
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:31
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:35
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit bb8e9d2 into master Sep 24, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant