Skip to content

feat(retry): Add retry logic with exponential backoff for HTTP requests#20

Merged
edenreich merged 7 commits intomainfrom
claude/issue-19-20250822-1009
Aug 22, 2025
Merged

feat(retry): Add retry logic with exponential backoff for HTTP requests#20
edenreich merged 7 commits intomainfrom
claude/issue-19-20250822-1009

Conversation

@edenreich
Copy link
Copy Markdown
Contributor

Implements retry logic with exponential backoff for HTTP requests as specified in issue #19.

Changes

  • Added RetryConfig type with configurable parameters
  • Updated ClientOptions to include retry configuration
  • Implemented exponential backoff with retry logic for all HTTP methods
  • Added comprehensive test coverage
  • Retry on transient errors (408, 429, 5xx) and network failures
  • Context cancellation support during retries

Closes #19

Generated with Claude Code

claude bot and others added 2 commits August 22, 2025 10:17
- Add RetryConfig type with configurable retry parameters
- Update ClientOptions to include RetryConfig field
- Implement retry helper functions for error detection and backoff calculation
- Add executeWithRetry method with exponential backoff logic
- Integrate retry logic into all HTTP methods (ListModels, ListProviderModels, ListTools, GenerateContent, GenerateContentStream, HealthCheck)
- Add comprehensive tests covering various retry scenarios
- Default retry configuration: enabled with 3 max attempts, 2s initial backoff, 30s max backoff, 2x multiplier
- Retry on status codes: 408, 429, 500, 502, 503, 504
- Retry on network errors: timeouts, connection failures, DNS errors
- Respect context cancellation during retries

Co-authored-by: Eden Reich <edenreich@users.noreply.github.com>
Signed-off-by: Eden Reich <eden.reich@gmail.com>
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.12.0-rc.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@edenreich
Copy link
Copy Markdown
Contributor Author

@claude Please analyse the changes and tell me if as a consumer of this library can I configure the status codes that are retryable ?

Also I would like to know if there is a callback that I can use to log out the retries in the consumer application for better visibility.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Aug 22, 2025

Claude finished @edenreich's task —— View job


Analysis of Retry Implementation

I've analyzed the retry implementation changes in this PR. Here are the answers to your questions:

Status Code Configurability

No, you cannot configure which status codes are retryable as a consumer of this library. The retryable status codes are hardcoded in the isRetryableStatusCode function in sdk.go:73-87:

func isRetryableStatusCode(statusCode int) bool {
    switch statusCode {
    case
        http.StatusRequestTimeout,      // 408
        http.StatusTooManyRequests,     // 429
        http.StatusInternalServerError, // 500
        http.StatusBadGateway,          // 502
        http.StatusServiceUnavailable,  // 503
        http.StatusGatewayTimeout:      // 504
        return true
    default:
        return false
    }
}

These status codes are fixed and cannot be modified through the RetryConfig.

Retry Logging/Callback Mechanism

No, there is no callback or logging mechanism available for consumers to monitor retry attempts. The current implementation:

  • Executes retries silently in the executeWithRetry function (sdk.go:169-208)
  • Does not expose any hooks, callbacks, or events for retry attempts
  • Provides no visibility into retry behavior for debugging or monitoring

🔧 Current RetryConfig Options

The RetryConfig struct in types.go:67-79 only allows configuring:

type RetryConfig struct {
    Enabled           bool  // Enable/disable retry logic
    MaxAttempts       int   // Max retry attempts (including initial)
    InitialBackoffSec int   // Initial backoff delay in seconds
    MaxBackoffSec     int   // Maximum backoff delay in seconds
    BackoffMultiplier int   // Multiplier for exponential backoff
}

💡 Suggestions for Enhancement

To address your needs, the retry implementation could be enhanced with:

  1. Configurable Status Codes:

    type RetryConfig struct {
        // ... existing fields
        RetryableStatusCodes []int // Custom list of retryable status codes
    }
  2. Retry Callback/Observer:

    type RetryCallback func(attempt int, err error, delay time.Duration)
    
    type RetryConfig struct {
        // ... existing fields  
        OnRetry RetryCallback // Called before each retry attempt
    }

These enhancements would provide the configurability and observability you're looking for without breaking existing functionality.

Todo List:

  • Read types.go to understand RetryConfig structure
  • Read sdk.go to analyze retry implementation
  • Check for status code configurability
  • Look for callback/logging mechanisms
  • Provide detailed analysis

@edenreich
Copy link
Copy Markdown
Contributor Author

@claude Sounds like a plan, let's implement these.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Aug 22, 2025

Claude finished @edenreich's task —— View job


Implementation Complete! 🎉

I've successfully implemented both requested enhancements to the retry logic:

Todo List

  • Read repository guidelines (CLAUDE.md)
  • Analyze current retry implementation
  • Design enhancements with backward compatibility
  • Implement configurable status codes
  • Implement retry callback mechanism
  • Update tests for new functionality
  • Run tests and lint
  • Commit changes

Configurable Status Codes

Added RetryableStatusCodes []int field to RetryConfig:

  • When nil/empty → uses default codes (408, 429, 500, 502, 503, 504)
  • When provided → only retries on your custom status codes

Retry Callback Mechanism

Added OnRetry func(attempt int, err error, delay time.Duration) field:

  • Called before each retry attempt
  • attempt: Retry number (1, 2, 3...)
  • err: The error that triggered the retry
  • delay: Backoff duration until next attempt

Usage Examples

Configure custom retryable status codes:

retryConfig := &RetryConfig{
    Enabled: true,
    MaxAttempts: 3,
    RetryableStatusCodes: []int{418, 422, 503}, // Only retry these
}

Add retry logging/monitoring:

retryConfig := &RetryConfig{
    Enabled: true,
    MaxAttempts: 3,
    OnRetry: func(attempt int, err error, delay time.Duration) {
        log.Printf("Retry attempt %d: %v (waiting %v)", attempt, err, delay)
    },
}

Combine both features:

retryConfig := &RetryConfig{
    Enabled:              true,
    MaxAttempts:          3,
    RetryableStatusCodes: []int{418, 503, 504},
    OnRetry: func(attempt int, err error, delay time.Duration) {
        metrics.IncrementRetryCounter()
        log.Printf("Retrying request (attempt %d): %v", attempt, err)
    },
}

Backward Compatibility

  • Existing code works unchanged
  • Default behavior preserved when new fields are nil/empty
  • All existing tests continue to pass

Comprehensive Testing

Added 4 new test functions with 15+ test cases covering:

  • Custom status code configurations
  • Retry callbacks with parameter validation
  • Combined features functionality
  • Nil callback handling
  • Backward compatibility scenarios

Results: All tests pass (21.045s) ✅ | Linter: 0 issues ✅

Changes Committed

  • types.go: Enhanced RetryConfig struct
  • sdk.go: Updated retry logic to use new fields
  • sdk_test.go: Added comprehensive test coverage

Commit: 1d3cefa - feat(retry): Add configurable status codes and callback mechanism


- Add RetryableStatusCodes field to RetryConfig for custom status codes
- Add OnRetry callback for retry event logging and monitoring
- Maintain backward compatibility with default behavior
- Update isRetryableStatusCode to use configurable codes
- Add comprehensive test coverage for new functionality

Co-authored-by: Eden Reich <edenreich@users.noreply.github.com>
* refactor(tests): Remove unnecessary blank line in TestIsRetryableStatusCode

Signed-off-by: Eden Reich <eden.reich@gmail.com>

* chore(release): 🔖 1.12.0-rc.1 [skip ci]

## [1.12.0-rc.1](v1.11.1...v1.12.0-rc.1) (2025-08-22)

### ✨ Features

* **retry:** Add configurable status codes and callback mechanism ([1d3cefa](1d3cefa))
* **retry:** Add retry logic with exponential backoff for HTTP requests ([0d1a57a](0d1a57a))

### ♻️ Improvements

* **retry:** Clean up comments ([b5a32db](b5a32db))
* **tests:** Remove unnecessary blank line in TestIsRetryableStatusCode ([223ab26](223ab26))

### 🔧 Miscellaneous

* Remove redundant comments ([fbbe49f](fbbe49f))

---------

Signed-off-by: Eden Reich <eden.reich@gmail.com>
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.12.0-rc.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

…unction

Signed-off-by: Eden Reich <eden.reich@gmail.com>
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.12.0-rc.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

* fix(headers): Remove redundant comment in TestWithHeaders

Signed-off-by: Eden Reich <eden.reich@gmail.com>

* refactor(retry): Remove comments from retryable status code tests for clarity

Signed-off-by: Eden Reich <eden.reich@gmail.com>

* chore(release): 🔖 1.12.0-rc.1 [skip ci]

## [1.12.0-rc.1](v1.11.1...v1.12.0-rc.1) (2025-08-22)

### ✨ Features

* **retry:** Add configurable status codes and callback mechanism ([1d3cefa](1d3cefa))
* **retry:** Add retry logic with exponential backoff for HTTP requests ([0d1a57a](0d1a57a))

### ♻️ Improvements

* **retry:** Clean up comments ([b5a32db](b5a32db))

### 🐛 Bug Fixes

* **headers:** Remove redundant comment in TestWithHeaders ([a6a6cbb](a6a6cbb))

### 🔧 Miscellaneous

* Remove redundant comments ([fbbe49f](fbbe49f))
* Testing the release ([#22](#22)) ([05b9687](05b9687))

* chore(release): 🔖 1.12.0-rc.1 [skip ci]

## [1.12.0-rc.1](v1.11.1...v1.12.0-rc.1) (2025-08-22)

### ✨ Features

* **retry:** Add configurable status codes and callback mechanism ([1d3cefa](1d3cefa))
* **retry:** Add retry logic with exponential backoff for HTTP requests ([0d1a57a](0d1a57a))

### ♻️ Improvements

* **retry:** Clean up comments ([b5a32db](b5a32db))
* **retry:** Remove comments from retryable status code tests for clarity ([5948350](5948350))
* **retry:** Remove redundant comments in isRetryableStatusCode function ([930dc15](930dc15))

### 🐛 Bug Fixes

* **headers:** Remove redundant comment in TestWithHeaders ([468f356](468f356))

### 🔧 Miscellaneous

* **release:** 🔖 1.12.0-rc.1 [skip ci] ([d36f1ab](d36f1ab))
* Remove redundant comments ([fbbe49f](fbbe49f))
* Testing the release ([#22](#22)) ([05b9687](05b9687))

* feat: Add Retry Mechanism section to README and implement parseRetryAfter function with tests also for rate-limiting retries

Signed-off-by: Eden Reich <eden.reich@gmail.com>

* chore(release): 🔖 1.12.0-rc.2 [skip ci]

## [1.12.0-rc.2](v1.12.0-rc.1...v1.12.0-rc.2) (2025-08-22)

### ✨ Features

* Add Retry Mechanism section to README and implement parseRetryAfter function with tests also for rate-limiting retries ([ce74bad](ce74bad))

---------

Signed-off-by: Eden Reich <eden.reich@gmail.com>
@edenreich edenreich merged commit b4a0ddb into main Aug 22, 2025
@edenreich edenreich deleted the claude/issue-19-20250822-1009 branch August 22, 2025 16:11
github-actions bot pushed a commit that referenced this pull request Aug 22, 2025
## [1.12.0](v1.11.1...v1.12.0) (2025-08-22)

### ✨ Features

* **retry:** Add retry logic with exponential backoff for HTTP requests ([#20](#20)) ([b4a0ddb](b4a0ddb)), closes [#22](#22)
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.12.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add Retry Logic with Exponential Backoff for HTTP Requests

1 participant