Skip to content

Extract internal/providerclient package — unify retry/backoff/429-handling across sentry/linear/notion/verda #24

@rafeegnash

Description

@rafeegnash

Problem

Three (soon four with verda) provider clients clone the same Do loop + 429 + backoff + jitter + retry classifier:

  • internal/sentry/client.go:177-415
  • internal/linear/client.go:140-291
  • internal/notion/client.go:118-249
  • internal/verda/client.go

Subtle divergence has already caused incidents:

  • Notion's isRetryableNetErr (line 240) silently drops "connection refused" + "no such host"; Sentry/Linear keep them as retryable → identical net errors retry on Sentry but bail on Notion
  • Linear's API sends RateLimit-Reset (absolute epoch) but the Linear client only reads Retry-After → bursts retry too soon, contributing to ban storms

Where

Listed above. Look for func (c *Client) Do(, parseRetryWait, sleepWithJitter, isRetryableNetErr.

Fix

Create internal/providerclient with:

type RetryPolicy struct {
    MaxRetries    int
    BaseBackoff   time.Duration
    MaxBackoff    time.Duration
    RetryStatuses []int  // [429, 502, 503, 504]
}

func Do(ctx context.Context, client *http.Client, req *http.Request, policy RetryPolicy) (*http.Response, []byte, error)
func ParseRetryWait(resp *http.Response, def time.Duration) time.Duration  // honors Retry-After, RateLimit-Reset
func IsRetryableNetErr(err error) bool  // canonical: timeouts + connreset + EOF, NOT DNS/refused

Each provider keeps its auth-header builder and response decoder. Migrate sentry/linear/notion first; verda last.

Acceptance criteria

  • internal/providerclient has 100% test coverage on retry logic (429, 5xx, network errors, Retry-After, RateLimit-Reset, context cancel)
  • All four providers route their requests through it
  • A behavioural test that the same network error is treated identically across all four

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions