You're troubleshooting a Python automation script that's supposed to be idempotent, safe to re-run, but a retry after a partial failure created duplicate AWS resources — what design mistakes commonly cause this, and how do you fix the script to actually be safe to re-run?
Quick Answer
The most common mistake is treating idempotent as meaning 'doesn't error on re-run' rather than 'produces the same end state regardless of how many times it runs' — a script that creates a resource without first checking whether an equivalent one already exists, or that doesn't use a client-side idempotency token, will happily create duplicates on every retry. Fix it by checking current state before acting, a describe-then-create pattern, and using idempotency tokens where the API supports them, so retries converge rather than duplicate.
Detailed Answer
This is like a form that says "click submit to place your order" with no confirmation of whether the order already went through — if the page seems to hang and you click submit again just to be safe, you might end up with two identical orders showing up at your door, because the system had no way to recognize "this is the same order being resubmitted" versus "this is a genuinely new order."
True idempotency, an operation that, no matter how many times you perform it, results in the same final state, requires deliberate design; it does not happen automatically just because a script "usually doesn't error." Automation scripts are retried constantly in production — a Lambda timing out mid-execution, a CI job getting killed by a runner restart, a network blip during an API call whose response never arrives even though the request succeeded server-side, and any of these can trigger an at-least-once retry, meaning the same logical operation may genuinely execute more than once against the same target.
The fix has two complementary layers. First, a describe-then-act pattern: before creating any resource, the script checks whether an equivalent resource already exists, by a stable identifier like a name tag or a deterministic naming convention derived from the input, not a randomly generated ID that changes every run, and only proceeds with creation if it's genuinely missing. Second, where the underlying API supports it, use client-side idempotency tokens — many AWS APIs, like EC2's RunInstances ClientToken parameter, let you pass a unique-per-logical-operation token that the API itself uses to deduplicate: if you retry with the same token, AWS recognizes it as the same request and returns the original result instead of creating a second resource, even if your first request's response was lost due to a network failure and you have no local memory of whether it succeeded.
In production, the describe-then-act pattern needs to handle a subtle race: if two instances of the automation run concurrently, which happens more often than expected, an overlapping cron schedule or a retried Lambda invocation racing the original before it's confirmed dead, both can pass the "doesn't exist yet" check before either creates the resource, resulting in a duplicate anyway. Mature automation adds a distributed lock, a DynamoDB conditional write, a Redis-style atomic set, or a cloud-native equivalent, around the describe-then-act sequence specifically to close this race window, not just the idempotency token alone.
The non-obvious gotcha is that idempotency tokens typically have a limited validity window, EC2's ClientToken, for example, is only guaranteed to deduplicate requests within several hours, so a retry that happens much later, say a manually re-triggered pipeline run days after the original failure using a script that generates a fresh token each time rather than persisting the token used for the original attempt, will not be deduplicated by the API at all, silently reintroducing the duplicate-resource problem the token was supposed to prevent. The token only protects against retries close in time to the original attempt, not against a human manually re-running the same logical job long after.