Sume public_reason: generation_rejected vs temporary error

How Sume picks generation_rejected, temporary_generation_error or generation_failed on a failed job from the provider HTTP status and the retryable flag.

4 min readSume
All posts

On a failed generation job, Sume derives public_reason from the provider's HTTP status and the retryable flag. A 4xx gives generation_rejected, a retryable failure gives temporary_generation_error, and generation_failed is the leftover for a 5xx that the worker marked not retryable.

This applies to the stored codes provider_execution_failed and generation_failed, after the typed reasons have had their turn. The logic is in packages/api-jobs/src/public-job.ts.

The five default outcomes

Sume reads the status from details.status, details.status_code or provider_http_status. A stored retryable boolean wins; when it is missing, the default is retryable for a 5xx and not retryable for a 4xx. These rows assume that default.

The stage on these rows is generation_processing unless the worker named a more specific stage. retry_after_seconds is filled only for retryable rows, rounded up and capped at 300.

Default outcomes of a failed execution, packages/api-jobs/src/public-job.ts (source read 2026-10-10)
Provider statuscategorypublic_reasonretryablenext_action
4xxgeneration_rejectedgeneration_rejectedfalseinspect_events
5xxgeneration_unavailabletemporary_generation_errortrueretry_later
5xx, stored retryable falsegeneration_unavailablegeneration_failedfalseinspect_events
None, stored retryable truegeneration_timeouttemporary_generation_errortrueretry_later
None, stored retryable falsegeneration_rejectedgeneration_rejectedfalseinspect_events

Typed reasons that win first

Before the defaults apply, the remap checks for failures it can name precisely. Any of these replaces the generic label and usually changes next_action to fix_input.

  • An artifact the storage refused as too large, covered in the artifact_too_large guide.
  • An input URL the provider could not download, which becomes input_media_unreachable.
  • A negative prompt sent to a model that does not take one.
  • Content-policy wording in the provider text, which becomes content_policy_rejected.
  • A failure at an image-input stage, which becomes image_content_rejected when it is not retryable.

Why a 4xx is not retried for you

A 4xx from the provider says the request itself was refused, so repeating the same body repeats the refusal. That is why generation_rejected is not retryable and points to inspect_events: the events and details.provider_error_message hold the reason, and the fix is in the request. A 5xx or a missing status says the provider or the worker stumbled, so waiting and trying again is reasonable.

A small decision function

A client can branch on the three public fields and ignore the rest. This function runs as written.

For the category names around these reasons, see the job error categories.

def decide(error):
    if not error.get("retryable"):
        if error.get("next_action") == "fix_input":
            return "fix the request"
        return "read events, then stop"
    wait = min(error.get("retry_after_seconds") or 30, 300)
    return "wait %s seconds, submit again" % wait

print(decide({"public_reason": "generation_rejected", "retryable": False,
              "next_action": "inspect_events"}))
print(decide({"public_reason": "temporary_generation_error", "retryable": True,
              "next_action": "retry_later", "retry_after_seconds": 20}))

Sources

Related posts

More in Developers

All Developers posts

Written by Sume