Adding an attachment changes the item's ETag – the next MERGE with the old ETag fails with 412
AttachmentFiles/add is a change to the list item: its @odata.etag changes. If you then update the item with IF-MATCH set to the ETag you read before the upload, SharePoint rejects it with 412 Precondition Failed. Re-read the item after the upload and continue with the new ETag.
AttachmentFiles/add je změna položky: její @odata.etag se změní. Když pak položku upravíš s IF-MATCH nastaveným na ETag z doby před nahráním, SharePoint to odmítne s 412 Precondition Failed. Po nahrání položku znovu přečti a pokračuj s novým ETagem.
Symptom
A common pattern: upload a photo as an attachment, then store its file name in a field of the same item.
const item = await getItem(id); // ETag "3"
await addAttachment(id, 'odometer-1a2b.jpg', bytes); // item is now ETag "4"
await update(id, { PhotoFile: 'odometer-1a2b.jpg' }, item.etag); // IF-MATCH: "3" → 412
The update fails with 412 Precondition Failed, and the UI typically tells the user that someone else changed the record – although nobody did. The attachment stays on the item, but the field that should point to it is never written, so a retry leaves an orphaned file behind.
Cause
Adding an attachment is a change to the list item, and the item's ETag changes with it. The ETag your code holds from the initial read is therefore stale the moment the upload succeeds, and optimistic concurrency does exactly what it should: it refuses the write.
Fix
Re-read the item after the upload and use its fresh ETag for the following update:
await addAttachment(id, name, bytes);
const fresh = await getItem(id); // new ETag
await update(id, { PhotoFile: name }, fresh.etag);
A clean way is to make the upload helper return the fresh item, so no caller can forget the re-read.
Notes
- Don't "fix" it with
IF-MATCH: *. That throws away the protection against two devices (phone and desktop) overwriting each other's changes – the reason the ETag is there in the first place. - Treat every other write to the same item that happens between your read and your MERGE the same way: re-read before you update with
IF-MATCH.