Safeguard
Developer Tools•

How should failed scan jobs retry? Backoff, dead letters and idempotency

Retry scan jobs only for transient failures, with exponential backoff and jitter, a hard cap and a dead-letter queue. Make every job safe to run twice.

Safeguard Security Team
4 min read

Retry only failures that can pass on a second try, such as timeouts and rate limit responses. Use exponential backoff with jitter, cap the attempts, and send exhausted jobs to a dead-letter queue. Make every job idempotent so a retry or duplicate never double counts findings.

Sort failures first

Not every failure deserves a retry. Retrying a permanent failure just burns workers.

FailureTransient?Action
Source host rate limitYesWait for the reset or retry-after time, then retry
Network timeout fetching codeUsuallyRetry with backoff
Worker crashed or was evictedYesJob becomes visible again, retry
Repository not found or access revokedNoMark failed, notify owner
Unsupported or corrupt archiveNoDead-letter with the error
Scanner crashed on this input every timeNo after two or three triesDead-letter, attach logs

Record a failure class on each attempt. That data later tells you whether most of your failures are your infrastructure or your inputs.

Backoff done properly

Exponential backoff means each wait is longer than the last, for example doubling. Jitter adds randomness so a thousand failed jobs do not all retry at the same instant. GitHub's own guidance for its API says to wait an exponentially increasing time between retries after a secondary rate limit and to stop after a set number of retries, and to honour the retry-after header when present. Apply the same logic to any external dependency your scanners call.

If the failure is a shared dependency, such as the source host being down, do not retry per job. Open a circuit breaker so all workers pause together and resume with a probe.

Idempotency

An idempotent job gives the same stored outcome whether it runs once or three times. Three tactics help.

  1. Give each job a dedupe key built from the target, scan type and config version.
  2. Write results with an upsert on that key rather than an append.
  3. Make side effects safe to repeat. A pull request comment should be updated in place, not posted again.

Without these, a visibility timeout that fires slightly early causes two workers to run the same job, and the developer sees duplicate comments and double findings.

Dead letters are a work queue

A dead-letter queue that nobody reads is a trash can. Assign an owner, alert on depth and age, and review it weekly. Each entry should hold the job, the failure class, the number of attempts and the last log excerpt. Add a replay action, so once you fix the cause you can push jobs back to the main queue.

Limits to set

  • Maximum attempts, commonly three to five, per failure class.
  • Maximum total time before a job is declared stale.
  • Maximum backoff delay, so a job does not wait for days.
  • A cap on replays from the dead-letter queue, to avoid loops.

Try it yourself

Click through it at your own pace, or open it full screen. Look at how repeated checks over time produce a single, updating view rather than duplicate entries.

Safeguard handles job scheduling and retries as part of its hosted scanning, so teams consuming it do not build this. You can try the free scan at https://gold.safeguard.sh/scan.

Quick check

How many retries are enough?

Three to five for transient failures is common. Past that, a human or a code fix is needed.

Why add jitter?

Without it, many failed jobs retry together and recreate the overload that caused the failure.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.