Skip to content

fix(sdk): classify Unavailable errors as side-effect free for retries - #694

Open
detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-sdk-classify-unavailable-errors-as-side-effect-b1c27a
Open

fix(sdk): classify Unavailable errors as side-effect free for retries#694
detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-sdk-classify-unavailable-errors-as-side-effect-b1c27a

Conversation

@detail-app

@detail-app detail-app Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Closes #691

Bug

ErrorCode::Unavailable was classified as side-effecting (has_no_side_effects() == false), but every server-side use of this code maps from StreamerMissingInActionError, which is raised before the append is submitted to the streamer actor (at lease acquisition or message-send time) — so no mutation could have occurred.

The impact: clients using AppendRetryPolicy::NoSideEffects would not retry a unary append that received Unavailable after the request body had already been sent (frame signal set). The retry logic falls back to err.has_no_side_effects(), which returned false, so the safe retry was blocked. This contradicts the inline comments in lite/src/handlers/v1/error.rs ("Unavailable error code promised to be side-effect free").

Fix

Move ErrorCode::Unavailable from the false branch to the true branch of ErrorCode::has_no_side_effects() in api/src/v1/error.rs. This matches the documented intent and the streamer backend's own internal retry behavior (backend/core.rs already retries on StreamerMissingInActionError).

The contrasting RequestDroppedError (which occurs after submission, where a mutation may have taken effect) continues to map to ErrorCode::Other and remain side-effecting — unchanged.

Testing

  • Added a unit test in api/src/v1/error.rs that requires every ErrorCode variant to be explicitly classified as side-effecting or side-effect free, guarding against future silent misclassifications as the #[non_exhaustive] enum evolves.
  • Added an SDK test verifying an Unavailable server error is both retryable and side-effect free through the ServerError/AppendError surfaces.
  • Extended the existing NoSideEffects retry-policy test to cover the core bug scenario: a signalled frame signal (body sent) + Unavailable error must now be safe to retry.
  • Routine checks pass: unit tests for s2-api (49) and s2-sdk (106), s2-lite backend/streamer/handler tests (225), clippy (default features), and cargo +nightly fmt --all --check.

End-to-end verification

Started a local s2-lite server (s2 lite --port 4243) and ran the SDK integration test suite using the same scoping as CI (cargo test -p s2-sdk --all-features -- --skip access_token --skip metrics): 77 tests pass, 0 failed, confirming no regression in append/read/list flows.

The unfiltered integration run has 26 pre-existing failures (all 501 not_implemented from the lite server for metrics and access-token-management endpoints); verified identical on the baseline before this change via git stash.

Not verified

The exact race window — a streamer actor exiting mid-request after guard acquisition but before the append acks — was not reproduced end-to-end. The streamer's 60s dormancy timeout and the lack of an internal shutdown hook in the black-box SDK integration harness make it non-deterministic to trigger at a precise point. The verifiable core of the bug (the retry decision under a signalled frame signal + Unavailable) is covered by the extended unit test.


Automatic Fixes PRs can be configured here.

@greptile-apps

greptile-apps Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR author is not in the allowed authors list.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Detail Bug] SDK retry: Unavailable errors are treated as side-effecting, blocking safe retries

0 participants