Warning Issued for 'Double-Write' Bug in Node.js Undici Fetch
A recent analysis exposes a subtle bug in Node.js's Undici fetch implementation that can cause silent double-writes. Certain request and response handling patterns risk data integrity, especially in transactional systems like payment flows or audit logs. The finding serves as a reminder for developers to carefully scrutinize the behavior of third-party HTTP clients.
The 'double-write' behavior isn't a traditional bug in Undici itself, but rather a fundamental reality of distributed systems. When a client-side error occurs, such as a timeout or a sudden connection reset, it's impossible to know if the server received and processed the request before the connection dropped. Retrying the request without this certainty is what leads to duplicate operations. Undici, the high-performance HTTP client that powers Node.js's native `fetch`, automatically retries certain requests. Its internal logic will re-send idempotent HTTP methods like `GET`, `PUT`, and `DELETE` if a failure is detected, assuming these operations can be safely repeated. This convenience, however, can create a false sense of security for developers. The critical distinction is between HTTP-level idempotency and business-logic idempotency. Undici handles the former, but a `PUT` request that successfully updates a database row might also trigger a non-idempotent side effect, like sending an email or logging an audit event. Retrying this `PUT` request would result in a duplicate side effect, even if the final database state is the same. The industry-standard solution for this problem is the use of idempotency keys. For any critical or transactional API call, the client generates a unique identifier (like a UUID) and includes it in the request headers, often as `Idempotency-Key`. This allows the server to recognize and safely discard replayed requests, ensuring an operation is processed only once. Payment processors like Stripe and cloud providers like AWS rely heavily on this pattern. If their servers receive a request with an idempotency key they've already processed, they don't re-run the operation. Instead, they simply return the original, cached response, guaranteeing that a network hiccup on the client-side doesn't lead to a customer being charged twice.