C.W.K.
Stream
Lesson 04 of 05 · published

No Automatic Retry

~11 min · retry, billing-ambiguity, idempotency, intentional

Level 0Empty Shelf
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"'Retry on failure' quietly assumes the failure did nothing. For a paid call, that assumption is a bet."

The Most Reflexive Good Practice, Turned Trap

Wrapping a network call in automatic retries is such standard hygiene that most HTTP clients do it by default. And for the operations retries were designed for — idempotent, side-effect-free reads — it's exactly right: the call either had no effect or you couldn't tell, and either way running it again is harmless. The paid transcription call is neither of those things. It moves money, and once submitted it may have already run. So Recall does something that looks wrong at first glance: it removes automatic retry entirely from the client that makes the paid call.

The reason is precise. A transport timeout after the request was submitted tells you nothing about whether the provider ran the job. The provider might have transcribed and charged, with only the response lost in transit. An automatic retry in that moment is a blind second purchase. The retry reflex, applied across the paid boundary, is a money leak dressed as resilience.

The Paid Boundary Is the Line

The fix isn't 'never retry' — it's 'know exactly where the irreversible act is, and never let an automatic retry cross it.' Recall draws a hard line at the paid POST:

  • Before the boundary — hashing the source, building the audio proxy, reserving on the ledger — nothing has been spent. A failure here is safe to retry, because the first attempt provably cost nothing. Recall exposes exactly this: a 'retry the failures that are known to be before the paid boundary' operation.
  • At or after the boundary — the POST went out. A failure here can't be proven harmless, so it is never auto-retried. It becomes the ambiguous state from the last lesson, escalated to a human.

So retries aren't banned — they're bounded. The engine retries freely on the cheap, provably-effect-free side and stops dead at the money boundary. The skill is knowing precisely where that boundary sits, because an automatic retry is only ever as safe as your proof that the first attempt did nothing.

Resilience Is Not the Same as Retrying

It's worth saying plainly, because the reflex runs deep: a system can be extremely resilient and still refuse to auto-retry. Recall survives crashes, disconnects, and provider timeouts — but it achieves that through durable reservations, an outbox, and idempotent acceptance, not through blindly re-hitting a paid endpoint. Real resilience is about making every state recoverable, not about hammering the same call until it works. When the operation is free and idempotent, retry away. When it spends money or can't be proven side-effect-free, resilience means recording enough to recover and handing the uncertainty to someone who can check the truth.

Code

Retry freely before the boundary; never across it·python
# The paid client has retries DISABLED on purpose.
scribe = HttpClient(retries=0)   # a timeout here is ambiguity, not a retry

# Failures are classified by which side of the money boundary they hit:
#
#   pre-paid-boundary  (hash, build proxy, reserve)  -> SAFE to retry
#   at/after paid POST (submitted, no response)       -> AMBIGUOUS, human only
#
# The operator can retry only the provably-cheap failures:
#   recall-archive retry-failed <batch> --safe-only
#     ^ re-runs ONLY failures known to be before the paid POST.
#       It will not touch anything that might already be billed.

External links

Exercise

Look at the HTTP or API clients in your own project. For each one, ask: does it retry automatically, and does the endpoint it calls have a side effect (payment, send, external mutation)? Find one that retries across a side-effect boundary. Then draw the boundary explicitly: what steps are provably effect-free (safe to retry) and where does the irreversible act happen (never auto-retry)? Redesign the retry policy to stop at that line.
Hint
Two questions expose the risk: (1) what is my client's default retry behavior — many retry idempotent methods silently? (2) can I prove the call had no effect when it timed out? If the answer to (2) is no, that call must not be auto-retried. Keep retries on the cheap preparation steps and convert post-submission failures into an explicit, human-resolved state.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.