The failure that puts two charges on a customer's card is mundane. A client sends a request, the provider completes the work, and the response is lost on the way back. The caller sees a timeout and cannot tell whether the request never arrived or succeeded and merely lost its reply. That uncertainty is the core problem, and a timeout does not mean nothing happened (Ali Aftab K. on idempotency design).
Stripe frames it the same way: a call between two nodes can fail at connection, mid-operation, or after success but before the client hears back, and in the ambiguous cases the client does not know whether retrying is safe (Stripe engineering on idempotency). Distributed networks fail at some ambient background rate, so the design assumption is that the same request will arrive more than once (Stripe engineering on idempotency).
For a human clicking a Pay button, this is annoying. For an autonomous agent that may retry a tool call several times in a loop, autonomously, at machine speed, it is a money leak. The agent does not get tired or notice the second charge. Nobody notices until the reconciliation report runs.
A second, subtler path to duplicate side effects is concurrency: two workers pick up the same job, or a message consumer processes the same event twice (Balmik Prajapati on idempotency keys in payment APIs). That is why the controls below stack. Caps limit total exposure, gates catch the unusual, idempotency makes each individual call safe to repeat. All three are needed; none substitutes for another.



