Skip to content

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

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_89d327b3-b883-4365-b6a3-46b6701342a9/bugs/bug_a32c31e7-b489-40e4-9e1b-ed456f9c265d

Introduced in #653 on Jul 31, 2026

Summary

  • Context: The ErrorCode::Unavailable is returned when a streamer actor becomes unavailable, which occurs before any operation is submitted to the backend.
  • Bug: ErrorCode::Unavailable is incorrectly classified as has_no_side_effects = false, but all current uses (StreamerMissingInActionError) have no side effects because no mutation occurred.
  • Actual vs. expected: Actual: has_no_side_effects() returns false for Unavailable. Expected: Should return true since the error occurs before any operation is submitted.
  • Impact: Clients using AppendRetryPolicy::NoSideEffects will not retry safe operations when receiving Unavailable errors, reducing resilience.

Code with Bug

// api/src/v1/error.rs
pub fn has_no_side_effects(self) -> bool {
    match self {
        // ... variants with no side effects ...
        Self::Unavailable  // <-- BUG 🔴 should be classified no-side-effects
        | Self::UpstreamTimeout => false,
    }
}

Explanation

  • All current ErrorCode::Unavailable responses come from StreamerMissingInActionError.
  • StreamerMissingInActionError is raised during streamer lease acquisition when the streamer is already closed, i.e. before any backend operation/message submission, so it cannot have caused a mutation.
  • When a unary append request has already sent its HTTP body, the SDK’s AppendRetryPolicy::NoSideEffects falls back to err.has_no_side_effects(). Because Unavailable incorrectly returns false, the client does not retry even though retry is safe.

Codebase Inconsistency

  • The handlers explicitly document intended semantics:
    // lite/src/handlers/v1/error.rs
    // Unavailable error code promised to be side-effect free,
  • The streamer backend differentiates “before submission” vs “after submission” failures, mapping the safe one to StreamerMissingInActionError:
    // lite/src/backend/streamer.rs
    .send(Message::Append { ... })
    .map_err(|_| StreamerMissingInActionError)?;  // before submission: no side effects
    let ack = reply_rx.await.map_err(|_| RequestDroppedError)??; // after submission: possible side effects

Recommended Fix

Change ErrorCode::Unavailable to return true in has_no_side_effects():

// api/src/v1/error.rs
pub fn has_no_side_effects(self) -> bool {
    match self {
        // ... existing true variants ...
        Self::Unavailable => true, // <-- FIX: treat as no-side-effects
        // ... existing false variants ...
        _ => false,
    }
}

History

This bug was introduced in commit ee23e99. The commit added the has_no_side_effects() method to ErrorCode but classified Unavailable as side-effecting (false), contradicting the earlier documented intent (commit c37c229) that Unavailable is “promised to be side-effect free.”

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions