Skip to content
Yunus Shaikh
Engineering note · 2026-10-31

When an AI Edit Is Based on an Older Draft

A stale AI edit can overwrite a newer human change. A fictional release-note example shows how strong ETags, If-Match, and a clear conflict path prevent that lost update.

By Yunus Shaikh · Published · 5 min read

A slow model call can outlive its source draft

An assistant reads a release-note draft, spends time proposing edits, then sends the result. During that gap, a person may save a newer version. If the tool replaces the whole document without checking what it read, it can erase that work. This is a lost update: the write is based on an older representation.

A fictional draft with two editors

Imagine an assistant reading a made-up release note. The API returns ETag: "a91c" with the draft. The assistant prepares a shorter summary. Before it saves, an editor changes the launch date and saves; the current representation now has ETag: "c30d". These tag strings are opaque validators, not numbers to sort. The assistant should tell the API which representation it used as its starting point.

Two safeguards answer different questions

  • A request identity such as an idempotency key asks whether this same command has already been applied, when the receiving endpoint records and honors that key. It helps resolve a repeated request after a lost reply.
  • If-Match with the exact strong ETag asks: is the representation I read still current? It prevents a write based on an older draft from silently replacing a newer one.
  • One does not replace the other. Reusing a request key does not detect a human edit made after the assistant read; an ETag does not establish whether a timed-out request already committed.
  • A later 412 can mean another editor saved first, or that the assistant’s first request already advanced the draft before its reply was lost. Resolve request outcome separately; the precondition alone does not identify the winner.

Make each save conditional

The assistant could send PATCH /drafts/release-note-42 with If-Match: "a91c". RFC 9110 requires the origin server to compare If-Match using strong comparison and evaluate the condition before performing the method. When no supplied tag matches, the server must not perform that update. It may return 412 Precondition Failed; the RFC also permits a success response when the server can determine that this same state-changing request already succeeded. Define that response contract clearly. For an ordinary stale edit, return a conflict and let the caller read the current representation before preparing another write. If-Match: * only checks that a representation exists; it does not confirm that the draft is still the one the assistant read.

A conflict needs a recovery path

When the service identifies a stale-base conflict, fetch the current text and show the proposed AI change beside it. If this response arrived after a lost reply, first resolve whether the earlier attempt committed; a stale validator alone cannot distinguish the assistant’s saved edit from a human edit. If the conflict is genuine and the application has a well-defined field-level merge rule, create a new proposal from the current representation and submit it with the new ETag. Otherwise ask a person to review the difference. Even a safe merge must use another conditional write; blindly retrying the old whole-document replacement with a fresh tag would turn stale content into an accepted update.

Keep the version check with the write

A handler that reads the ETag, checks it in application code, then saves in a separate unguarded operation leaves room for a second editor to write between the check and the save. Make the expected-version check part of the database update itself, or use an equivalent transaction with locking or serialization that prevents a competing write between the check and save. Opening an ordinary transaction around an unguarded read followed by a write is not enough. AWS’s DynamoDB guide shows one vendor-specific pattern: store a version attribute and include an equality condition in the update. Check the actual database and replication mode in your system; an HTTP header by itself does not make storage updates atomic.

Acceptance cases for a fictional edit tool

  1. With no intervening edit, submit the current strong ETag and a proposed change. The update succeeds once and returns the validator for the new representation.
  2. Save a human edit after the assistant reads the draft, then submit the assistant’s old ETag. The stale update is rejected and the saved human version remains intact.
  3. Send two different text changes simultaneously, both based on the same ETag, with each successful write advancing the validator. At the persistence boundary, only one conditional write can win; the other receives the documented conflict. Check this against the real store semantics.
  4. After a known conflict, refresh and show a diff. Merge only under an explicit safe policy; otherwise ask the person. Do not loop by refreshing the tag and resubmitting the old complete document.
  5. Commit an update, then drop its response. On resubmission, verify that request-identity or status lookup can distinguish the already-completed action. An old ETag and a 412 alone do not reveal whether the first attempt committed.

A matching ETag is not an approval

A matching strong ETag establishes that the representation has not changed in a way covered by that validator; it does not establish that the AI’s proposed text is correct, authorized, or acceptable. Keep application validation and any required human approval in place. The release-note example and endpoint are fictional, not claims about an implemented EasyKOT or FinancialCents endpoint. These acceptance cases are proposed design checks, not executed tests.

More writing →