Skip to content

feat(scheduler): bounded retry for transient Threads API errors + carousel failure enrichment (#257) - #257

Merged
ajianaz merged 1 commit into
developfrom
feat/transient-retry
Sep 10, 2026
Merged

ajianaz merged 1 commit into
developfrom
feat/transient-retry

Conversation

@ajianaz

@ajianaz ajianaz commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

What

  • Migration 018: schedules.attempt_count (int, default 0) + schedules.next_due_at (nullable RFC3339 UTC eligibility gate).
  • Scheduler: on publish failure, classify via TitenError::is_transient_message. Transient + non-bundle + attempts < 2 → defer 10 minutes (pending + [retry N] error marker); otherwise fail permanently with attempt count persisted.
  • Classifier: transient = OAuthException feat: fix Docker files + add release workflow #1 (Meta catch-all — killed carousel child creation on 2026-09-08 and 2026-09-10 while TEXT/IMAGE kept publishing), OAuthException create_post tidak handle VIDEO media type #24 (observed transient 2026-08-25), HTTP 429/5xx. Permanent = fix: register migration 008 in store.migrate() #100, release: v0.7.4 #190, validation errors.
  • get_due_schedules: next_due_at overrides scheduled_at when set — otherwise a retry would fire on the very next tick and the backoff would never hold. NULL keeps legacy behavior.
  • Publisher: carousel child-container failures now surface WHICH child failed (idx/total), the offending URL, and orphaned child IDs.

Why

Production audit of the 2026-09-08 + 2026-09-10 carousel failures (OAuthException #1 at child-container creation) showed Meta-side transient errors permanently kill a scheduled slot with zero recovery — the identical content republishes fine on a later attempt. Two carousel slots died silently; both failed on different Titen builds (v0.9.1 process and v0.9.2), ruling out a code regression.

Testing

  • 7 new unit tests for is_transient_message (transient + permanent cases).
  • 4 new integration tests (store_retry.rs): deferral sets a future gate and hides the row from get_due_schedules; gate expiry re-exposes it; legacy rows (NULL gate) keep scheduled_at semantics; final failure persists attempt_count and clears the gate.
  • cargo test --workspace green; cargo clippy --workspace --all-targets -- -D warnings clean; cargo fmt clean.

…ousel failure enrichment (#257)

## What

- MIGRATION 018: schedules.attempt_count (int, default 0) +
  schedules.next_due_at (nullable RFC3339 UTC eligibility gate).
- SCHEDULER: on publish failure, classify the error via
  TitenError::is_transient_message. Transient + non-bundle + attempts < 2
  defer the schedule 10 minutes (pending + [retry N] error marker);
  otherwise fail permanently with attempt_count persisted.
- CLASSIFIER: transient = OAuthException #1 (Meta catch-all, killed
  carousel child creation on 2026-09-08 and 2026-09-10 while TEXT/IMAGE
  kept publishing), OAuthException #24 (observed transient 2026-08-25),
  HTTP 429/5xx. Permanent = #100, #190, validation errors.
- GET_DUE_SCHEDULES: next_due_at overrides scheduled_at as the
  eligibility gate when set — otherwise a retry would fire on the very
  next tick and the backoff would never hold. NULL keeps legacy behavior.
- PUBLISHER: carousel child-container failures now surface WHICH child
  failed (idx/total), the offending URL, and orphaned child IDs — every
  prior failure looked identical and required guesswork.
- Bundle members are excluded from retry: a chain that breaks mid-flight
  fails the whole chain (existing semantics preserved).

## Why

Production audit of 2026-09-08 + 2026-09-10 carousel failures
(OAuthException #1 at child-container creation) showed Meta-side
transient errors permanently kill a scheduled slot with zero recovery —
the identical content republishes fine on a later attempt. Two
back-to-back carousel slots died silently; the same rows failed on two
different Titen builds, ruling out a code regression.

## Testing

- 7 new unit tests for is_transient_message (transient + permanent cases)
- 4 new integration tests (store_retry.rs): deferral sets a future gate
  and hides the row from get_due_schedules; gate expiry re-exposes it;
  legacy rows (NULL gate) keep scheduled_at semantics; final failure
  persists attempt_count and clears the gate.
- cargo test --workspace green; clippy -D warnings clean; fmt clean.
@github-actions

Copy link
Copy Markdown

🔍 Cora AI Code Review

⚠️ Review could not complete. LLM API error after retries: Unknown error — check workflow logs


Review powered by cora-code · BYOK · MIT

@ajianaz
ajianaz merged commit 2ce50d2 into develop Sep 10, 2026
13 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