Skip to content

IdempotencyGuard: crash between side effect and record() still duplicates on retry #1

Description

@impartshadow

IdempotencyGuard.record() appends to the ledger after the side effect returns. A crash in the window between the external call succeeding and the ledger append leaves no evidence of the call, so the retry re-executes it — the exact duplicate the contract exists to prevent.

The window is small but it is the highest-consequence one: it is open precisely while the payment/email/webhook is in flight.

Fix is a pending/terminal split:

  1. Before the external call, append a pending record keyed on the same key_for(ctx).
  2. After it returns, append a terminal record with the result, linked by the same key.
  3. check_pre blocks on either. A pending hit means "a prior attempt reached the external system and we do not know the outcome" — which must block re-execution and surface for reconciliation, not silently replay a result we never recorded.

Point 3 is the part that matters: a pending hit is not the same as a terminal hit, and collapsing them either duplicates the effect or fabricates a result. Violation.recovery should say which case it is.

Prior art for the same split: idempotency-ref-v1 in giskard09/argentum-core (docs/spec/idempotency-ref.md), which separates the pre-call reservation from the post-call anchor for this reason.

Surfaced while answering the durability question in crewAIInc/crewAI#5802. Relates to FM-037.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions