SEO · Content Systems · Publishing Infrastructure

How to Design Status Transitions for a Content Generation Queue

Learn how to model pending, reserved, generated, published, failed, repair, skipped, and archived states in a content generation queue with clear, safe transitions.

A content generation queue fails in practice when its statuses are too vague.

If "pending" means scheduled, ready, and unclaimed all at once, workers will race each other. If "failed" means transient API error, invalid brief, and blocked by policy, retry logic becomes guesswork. If "published" is used both for "content exists" and "content is live on the site," the queue stops being a source of truth and turns into a pile of ambiguous labels.

The fix is not more statuses. It is a small set of states with explicit transitions and a clear rule for what each transition means.

Start by separating lifecycle from outcome

A content generation queue is not the whole publishing system. It usually sits between topic selection and publication, so its statuses should describe work in motion, not every possible business condition.

A practical set is:

  • pending
  • reserved
  • generated
  • published
  • failed
  • repair
  • skipped
  • archived

Those states are enough if you define them strictly.

  • pending means the item is eligible to be worked on but has not been claimed.
  • reserved means a worker has claimed it and is actively processing it.
  • generated means the content payload exists and passed whatever generation-step checks you require, but it has not yet been published.
  • published means the content is live in the destination system.
  • failed means the current attempt ended in an error that needs a decision.
  • repair means the item is known to be broken or incomplete and is waiting for human or automated correction before another attempt.
  • skipped means the item will not be processed under current rules, usually because eligibility changed.
  • archived means the item is closed out and should no longer be picked up by workers or displayed in active queues.

If you need to retain richer detail, store it as fields like failure_reason, retry_count, reserved_at, published_at, archived_at, and an immutable event log. Do not solve detail by multiplying states.

The valid transitions should be boring

The healthiest queue machines are the ones with few legal edges.

A sensible transition graph is:

  • pending → reserved
  • reserved → generated
  • reserved → failed
  • reserved → skipped
  • generated → published
  • generated → repair
  • generated → failed
  • failed → pending
  • failed → repair
  • failed → skipped
  • repair → pending
  • repair → skipped
  • repair → archived
  • skipped → archived
  • published → archived

You may also allow generated → archived if generated output becomes obsolete before publication, but that should be a deliberate business decision, not a silent cleanup path.

What should not be allowed is more important than what should.

  • pending should not jump straight to published.
  • published should not return to generated.
  • archived should not transition anywhere.
  • skipped should not be reprocessed automatically.
  • failed should not auto-loop forever without a retry policy.

That last point matters. Infinite retry loops usually mean the queue is hiding a permanent problem behind a transient-looking state.

This is why safe retry behavior for failed AI content generation jobs belongs in the same design discussion. Retry is a transition policy, not just a worker setting.

Reserve is not the same as lock forever

reserved should mean "this worker owns the item for now," not "nobody else can touch this row until someone remembers it tomorrow."

Use a reservation lease with an expiration timestamp.

A typical pattern is:

  1. Worker selects one pending item.
  2. Worker atomically changes it to reserved and sets reserved_by plus reserved_until.
  3. Worker processes the item.
  4. Worker moves it out of reserved into generated, failed, or skipped.

If the worker dies, the lease expires and the item becomes eligible for recovery. You can either move expired reservations back to pending with a sweeper job or treat them as a special subset of failed depending on how much diagnostic history you want to preserve.

The important thing is that reservation is temporary and observable. A stuck reserved item is a useful signal, not a mystery.

Treat generated as a real checkpoint

In many systems, generated is the state that gets skipped because teams want to jump from queue to publish.

That usually creates trouble.

Keeping generated as an explicit state gives you a place to store the content payload, validation results, and any final checks before publication. It also creates a clean boundary between generation failures and publishing failures.

That separation is useful when you need to debug whether a bad page came from prompt output, template assembly, validation, deploy, or CMS publish logic. It also lines up with the principle behind separating content generation from publishing.

A good rule: if the content exists in a form that could be reviewed, rendered, or published, it deserves its own state.

Distinguish failed, repair, and skipped

These three states often get collapsed into one another, but they serve different operational purposes.

failed is for an attempt that could not complete. The item may still be valid, but something went wrong during generation, validation, storage, or publish handoff.

repair is for items that need intervention before another attempt makes sense. Examples include malformed source data, missing required fields, a broken template, or a page that now conflicts with a newer topic rule.

skipped is for items that should not continue in the queue as currently defined. The reason might be business logic, duplicate topic detection, eligibility changes, or a decision that the page is no longer worth publishing.

That distinction matters because each state implies a different next action:

  • failed may retry or move to repair after repeated attempts.
  • repair needs a fix before it can return to pending.
  • skipped should usually be closed and archived, not retried.

If you already have topic validation upstream, this is where duplicate topic detection before generating SEO content helps prevent queue contamination. A clean queue is easier to reason about than one filled with items that should never have been admitted.

published should be terminal for the queue, not for the content

Once an item is published, the queue item's job is done.

That does not mean the page is immutable. It means the queue no longer owns the active lifecycle. Updates, refreshes, and future re-generation should usually create a new queue item or a distinct revision record rather than mutating the old published job back into an active state.

This is one of the easiest ways to avoid state drift:

  • queue item tracks work
  • content record tracks current page state
  • publish record tracks what was sent live

If you need to audit behavior later, the queue should show how a page arrived at published, while the content store should show what is currently live. That separation is especially helpful when combined with a proper schema design, like the one described in how to design database tables for an SEO publishing pipeline.

Use events to explain transitions

Statuses alone are not enough for debugging.

Store each transition as an event:

  • from state
  • to state
  • actor or worker id
  • timestamp
  • reason code
  • optional payload diff or error reference

That makes it possible to answer questions like:

  • Why did this item go from generated to repair?
  • Which worker reserved it?
  • How long did it remain reserved?
  • Did a publish failure happen after generation succeeded?
  • Was it skipped because of eligibility, duplication, or policy?

Without transition events, the queue becomes a static snapshot. With them, it becomes an operational log.

A practical state policy

If you want a conservative default, implement the queue with these rules:

  • Only workers can move pending → reserved.
  • Only the owning worker can move reserved onward, unless a sweeper reclaims expired reservations.
  • Human review can move generated → repair or skipped.
  • Automated validators can move generated → failed.
  • Retry logic can move failed → pending up to a defined limit.
  • Repair completion can move repair → pending.
  • Publishing can move generated → published.
  • Any terminal cleanup job can move published, skipped, or failed into archived once the record is no longer operationally active.

That is enough to support a queue that is safe to retry, safe to inspect, and hard to corrupt by accident.

The main design choice is not the exact list of labels. It is whether each label has one meaning, one next action, and one owner. If those three things are true, the queue can scale without becoming a second, weaker CMS.

← Back to SEO Infrastructure