One Timeout, Two Orders: How to Make AI Actions Safe to Retry

Illustrated infographic summarizing: One Timeout, Two Orders: Make AI Actions Safe to Retry

By Greg Nowak. Updated 25 August 2026.

An AI agent submits an order. The supplier accepts it, but the response disappears during a network timeout. The agent sees a failed call and tries again. Unless the integration recognises both attempts as one business action, the customer may receive two orders.

This is not limited to autonomous agents. The same failure can duplicate invoices, CRM records, support tickets, emails and webhook-driven jobs. A timeout means only that the caller did not receive an answer in time. It does not tell you whether the action happened.

Decide what a retry is allowed to repeat

Retries are useful for temporary network failures and overloaded services. Backoff, jitter and attempt limits control when requests are repeated, but they do not prevent repeated business effects. That protection must be part of each tool’s contract.

Operation Safe retry behaviour Minimum safeguard
Read product or account data Repeat after a temporary failure Backoff, jitter and an attempt limit
Create or update a record Return the first resource or result Stable idempotency key and stored status
Place an order or send a message Never repeat an accepted action Provider retry key, plus reconciliation
Receive a webhook Acknowledge a previously claimed event Atomic event claim and idempotent worker
Start slow work Report that the original job is still running Durable queue, job ID and status endpoint
A retry policy should be based on the operation’s business effect, not merely its HTTP method.

Give one business intention one stable key

An idempotency key identifies the intended action, not an individual HTTP attempt. Create it when the workflow decides to perform the action, save it with the workflow state and reuse it for every retry. Generating a new UUID inside the retry loop defeats the protection because every attempt then appears unrelated.

Keep the material request unchanged. If the amount, recipient or product changes, create a new intention and therefore a new key. Conversely, reject a reused key whose request fingerprint differs from the original.

Provider behaviour varies. Stripe’s API v1 documentation says that it stores the first status and response body for a key and compares later parameters with the original request. LINE’s messaging guidance requires the retry key on the first attempt and blocks a later request after the original has been accepted. Both also impose retention rules, so your own ledger may need to remember an intention for longer than the provider does.

Make the ledger durable and the claim atomic

A useful action ledger records the key, operation type, request hash, status, timestamps, attempt count, provider request ID and completed result. The database—not process memory—should decide which worker owns the action.

For PostgreSQL, a small claim can use a primary key and ON CONFLICT DO NOTHING:

INSERT INTO action_ledger
  (idempotency_key, operation, request_hash, status)
VALUES
  (:key, :operation, :request_hash, 'processing')
ON CONFLICT (idempotency_key) DO NOTHING;

If the insert succeeds, that worker owns the work. If it does not, read the existing row: return its stored result when completed, report that it is still processing, or apply an explicit recovery policy. A separate “check, then insert” sequence is unsafe because two workers can pass the check simultaneously. PostgreSQL documents ON CONFLICT as the database-level alternative for handling concurrent uniqueness conflicts.

Store the first useful response or a stable resource identifier. “Already processed” is often too vague for an agent; returning the original order ID gives it a safe way to continue the workflow.

Know the gap a ledger cannot close

A local ledger alone cannot guarantee exactly-once execution against an unrelated external system. A worker can call the supplier successfully and crash before marking its ledger row complete. On recovery, the system still does not know whether to call again.

The strongest design passes the same key to a provider that supports idempotency. If that is impossible, use a unique business reference the provider can query, or introduce a reconciliation state such as outcome_unknown and stop automatic retries until the result is checked. For consequential actions, an honest uncertain status is safer than guessing.

Queues help throughput, not duplicate prevention

Inbound webhooks present the problem in reverse: providers may deliver the same event more than once. Verify the signature, atomically claim the event identity, enqueue the work and acknowledge promptly. The worker must remain idempotent because queue messages can also be retried.

Stripe’s current webhook guidance recommends recording processed event IDs, accounting for logically duplicated events and handling work asynchronously. The queue separates receipt from execution; the ledger prevents two deliveries from producing the same effect.

Test success followed by silence

The most revealing test is not “the API returned an error.” Simulate the downstream service accepting an order and then dropping the response. Allow the agent or worker to retry and verify that only one business action exists.

  • Send two simultaneous requests with the same key.
  • Reuse a key with a changed payload and confirm it is rejected.
  • Crash after the external call but before the local completion update.
  • Retry after the provider’s key-retention window.
  • Trace workflow ID, action key, attempt, provider request ID and final status together.

Start with the actions that can cause damage

Inventory every agent tool, scheduled job and webhook. Mark reads, reversible writes and consequential actions separately. Then document who creates the key, where it is stored, how long it survives, what response is replayed and how an uncertain outcome is reconciled.

Greg can help your team map these failure boundaries and build a reusable gateway for stable keys, atomic claims, result replay and operational logging. If retries currently depend on hope or manual cleanup, get in touch for a practical review.

Retries should repeat delivery attempts, not business decisions. With that distinction enforced, a timeout remains an operational nuisance instead of quietly becoming a second order.

Related on GrN.dk

Need help with this kind of work?

Make your AI integrations safe to retry Get in touch with Greg.

Sources

Latest articles

NGINX 1.31.5 can route on JSON body values. Here’s how to weigh the performance, security, and operational trade-offs before using it.

OpenAI can keep agent sessions running, but reliable workflows still depend on clear failure states, safe retries, validation, limits and human fallback.

AI can identify termination deadlines and price adjustments in supplier contracts, route uncertain findings for approval and create the right reminders.

Why a DNS record can exist in a dashboard yet fail publicly—and how to trace zone cuts, verify glue, and fix the right side of a live delegation.

An Apache version below 2.4.68 may still be patched. Package provenance, vendor advisories, module checks and runtime evidence reveal the real position.

PHP 8.2 security support ends on December 31, 2026. Here is how to audit, test, and migrate a mixed CMS estate without rushing production changes.

How Danish businesses can automate Gmail and Microsoft 365 with rapid sorting, limited permissions and human approval.

When WordPress jobs run late, check WP-Cron and queue capacity first. Diagnose triggers, handlers, and Action Scheduler without guesswork.

WordPress 7.1 makes speculative loading configurable. Here’s how to spot overlapping rules and test speed gains without adding hidden costs.

Multiple records for the same customer in HubSpot? Learn how CVR number matching, AI suggestions and human approval can help you clean up duplicates while keeping track of fields, associations and customer history.