CLI retries non-idempotent writes after ambiguous gateway responses #814

Open
opened 2026-10-02 13:14:29 +00:00 by kayg · 1 comment
Owner

Context: #427 rev-mcp-api review, #484, DESIGN §41. Source base c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Priority: High. Source review only; product code is unchanged.

Evidence:

  • crates/calternal-sync/src/remote.rs:39 checks safe methods only for transport errors; :45 retries 429, 502, 503 and 504 for every clonable request.
  • crates/calternal-cli/src/remote_commands.rs:311 uses that policy for every generated action; :390 also applies it to ergonomic commands. crates/calternal-cli/src/main.rs:47 defaults to three retries.
  • crates/plugins/notes/src/lib.rs:3410 creates a fresh Note identity on each request; :4349 allocates fresh Log block IDs for each batch. The contracts for these actions have no request-operation identity.

Impact:

A gateway response can arrive after an upstream write committed. The CLI can then replay the same create and create a second item. Source evidence confirms the unconditional replay path; no live duplicate was produced in this review.

Expected:

Do not automatically replay non-idempotent writes after an ambiguous result. Use a server-recognized operation identity and receipt when #667 supplies them. Until then, retain bounded retries for safe reads and provide an actionable unknown-write-result error for ambiguous writes.

Regression test idea:

Use a test-only fault proxy that forwards one ordinary create, records its successful response, and returns a transient gateway status to the CLI. Assert one created item. Keep safe-read retry coverage and existing status expectations.

Duplicate check:

Searched all issue states for send_with_retry, retry and idempotency; read closed #350 and #460, and open #667. #667 owns the server receipt contract. This issue owns the concrete current CLI replay policy and depends on #667 for receipt-based retries.

Context: #427 rev-mcp-api review, #484, DESIGN §41. Source base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Priority: High. Source review only; product code is unchanged. Evidence: - `crates/calternal-sync/src/remote.rs:39` checks safe methods only for transport errors; `:45` retries 429, 502, 503 and 504 for every clonable request. - `crates/calternal-cli/src/remote_commands.rs:311` uses that policy for every generated action; `:390` also applies it to ergonomic commands. `crates/calternal-cli/src/main.rs:47` defaults to three retries. - `crates/plugins/notes/src/lib.rs:3410` creates a fresh Note identity on each request; `:4349` allocates fresh Log block IDs for each batch. The contracts for these actions have no request-operation identity. Impact: A gateway response can arrive after an upstream write committed. The CLI can then replay the same create and create a second item. Source evidence confirms the unconditional replay path; no live duplicate was produced in this review. Expected: Do not automatically replay non-idempotent writes after an ambiguous result. Use a server-recognized operation identity and receipt when #667 supplies them. Until then, retain bounded retries for safe reads and provide an actionable unknown-write-result error for ambiguous writes. Regression test idea: Use a test-only fault proxy that forwards one ordinary create, records its successful response, and returns a transient gateway status to the CLI. Assert one created item. Keep safe-read retry coverage and existing status expectations. Duplicate check: Searched all issue states for `send_with_retry`, `retry` and `idempotency`; read closed #350 and #460, and open #667. #667 owns the server receipt contract. This issue owns the concrete current CLI replay policy and depends on #667 for receipt-based retries.
Author
Owner

Confirmed the replay path from the issue evidence: send_with_retry cloned all requests and retried transient gateway statuses without checking for a safe HTTP method. It now retries safe reads only; a write that gets a transient gateway response or ambiguous transport failure returns an explicit unknown-result error. Added local capture-server tests that assert one write and retained safe-read retry coverage. Rust gates are pending the current OpenAPI build.

Confirmed the replay path from the issue evidence: `send_with_retry` cloned all requests and retried transient gateway statuses without checking for a safe HTTP method. It now retries safe reads only; a write that gets a transient gateway response or ambiguous transport failure returns an explicit unknown-result error. Added local capture-server tests that assert one write and retained safe-read retry coverage. Rust gates are pending the current OpenAPI build.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#814
No description provided.