A strong answer
A timeout only means the client did not receive a response. The server may already have committed the operation. I would define an idempotency contract in which the client sends a unique key for one logical action and reuses it for retries.
The server scopes the key to the authenticated caller and operation, stores a request fingerprint, and returns the stored result for a matching completed request. Reusing the key with different input should fail clearly. The side effect and result record need a crash-safe relationship. If both are in one database, a transaction may coordinate them; if another service is involved, use an outbox or a downstream idempotency contract rather than assuming one local transaction covers the network.
Bound retries with timeouts, backoff, jitter, and attempt limits. Define key retention and concurrent duplicate behavior. A trace ID is useful for observability but does not prevent duplicate effects.
Follow-up direction
Draw the timeline where the server commits but the response is lost, then show what the second request returns.