Sending a capture: no Idempotency-Key, so a lost reply turns a retry into a second capture and notification #21
Labels
No labels
bug
documentation
enhancement
performance
security
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-chromium#21
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo-chromium at
7998de372, reading the outbox against the core's capture endpoint at postulo 95348908d.Since postulo/postulo#230 the core honours an
Idempotency-Keyheader onPOST /api/v1/captures: a request repeated under the same key gets the first answer back, with no second capture and no second notification. The extension never sends one, while its delivery is at-least-once by design: the popup sends at once, the background retries every few minutes and at start-up, and the captures page retries when opened. A capture that Postulo created but whose answer never reached the browser's store is therefore sent again and created twice.src/lib/api.js:57-65,headers(): onlyAuthorization,AcceptandContent-Type; nothing insrc/mentions an idempotency key.src/lib/api.js:171-187,capture(): the POST, aborted afterTIMEOUT_MS(20,000 ms,api.js:147).src/lib/outbox.js:53-70,deliver(): the record becomessentonly after the response has been read andstore.updatehas run; until then it stayswaitingand is picked up by the next delivery.src/popup.js:158-160: Send saves the record aswaitingand delivers from the popup itself.store.jsitself notes), ending the delivery after Postulo has created the capture but before the record is markedsent.The same happens whenever a capture takes Postulo longer than 20 seconds (a slow instance, or many notifiers): the client aborts, the server finishes, and the retry duplicates it.
Idempotency-Keywith every capture POST, derived from the record: itsid(already acrypto.randomUUID(),store.js:26) plus a revision that changes whenever the person edits the record, because the server answers a reused key with a different payload by 422idempotency-key-reused. InkindOfFailure(api.js:212-224), treat a 409 (idempotency-key-in-use, the first attempt still being answered) aslaterrather thanrefused. Related to postulo/postulo#230, which added the header on the server and is closed.Done when
test/outbox.test.mjs, delivering the same waiting record twice (the first attempt's reply lost) sends the sameIdempotency-Keyboth times, and editing the record between the attempts changes it.test/api.test.mjs, a 409 answer is classed aslater.