# How Forge Handles API Request Retries: Configuration and Environment Variables

> Learn how Forge handles API request retries with a two-layer system and exponential backoff. Discover how to configure retry attempts and backoff duration using environment variables like FORGE_RETRY_MAX_ATTEMPTS for robust int...

- Repository: [Forge Code/forgecode](https://github.com/antinomyhq/forgecode)
- Tags: how-to-guide
- Published: 2026-04-08

---

**Forge implements a two-layer retry system that classifies provider errors as retryable and applies exponential backoff using parameters controlled by environment variables like `FORGE_RETRY_MAX_ATTEMPTS` and `FORGE_RETRY_INITIAL_BACKOFF_MS`.**

Managing transient failures when calling LLM APIs is critical for reliability. In the `antinomyhq/forgecode` codebase, Forge provides a robust **API request retry mechanism** that automatically handles rate limits and temporary outages through configurable exponential backoff. This article examines the architecture, source code implementation, and environment variables that govern how Forge retries failed requests.

## How Forge's Two-Layer Retry System Works

Forge's retry mechanism operates through two distinct layers working in sequence to ensure failed API calls are retried only when appropriate.

### Error Classification Layer

Located in [`crates/forge_repo/src/provider/retry.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_repo/src/provider/retry.rs), the `into_retry` function examines raw provider errors (from OpenAI, Anthropic, Google, Bedrock) and wraps them as `DomainError::Retryable` when appropriate. This conversion logic inspects HTTP status codes, transport-level failures, and special Anthropic overload signals.

The classification checks:
- HTTP status codes against `retry_config.status_codes` (extracted via `get_req_status_code`, `get_event_req_status_code`, or `get_api_status_code`)
- Transport errors including timeouts, connection resets, and premature stream closure
- Anthropic "overloaded" SSE events

```rust
if let Some(code) = get_req_status_code(&error)
    .or(get_event_req_status_code(&error))
    .or(get_api_status_code(&error))
    && retry_config.status_codes.contains(&code)
{
    return DomainError::Retryable(error).into();
}

```

All provider modules ([`openai.rs`](https://github.com/antinomyhq/forgecode/blob/main/openai.rs), [`anthropic.rs`](https://github.com/antinomyhq/forgecode/blob/main/anthropic.rs), [`google.rs`](https://github.com/antinomyhq/forgecode/blob/main/google.rs), [`bedrock.rs`](https://github.com/antinomyhq/forgecode/blob/main/bedrock.rs)) invoke `into_retry` when mapping request errors to domain errors, ensuring a uniform retry policy across providers.

### Retry Execution Layer

Found in [`crates/forge_app/src/retry.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_app/src/retry.rs), the `retry_with_config` function implements the actual retry logic using the **backon** crate. It constructs an `ExponentialBuilder` from `RetryConfig` parameters and executes the asynchronous operation with exponential backoff. The `should_retry` predicate ensures only `DomainError::Retryable` variants trigger retries.

## RetryConfig Structure and Defaults

The `RetryConfig` struct defined in [`crates/forge_config/src/retry.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_config/src/retry.rs) controls all retry behavior with the following fields:

```rust
pub struct RetryConfig {
    pub initial_backoff_ms: u64,   // initial delay before first retry
    pub min_delay_ms: u64,        // lower bound for each back-off step
    pub backoff_factor: u64,      // exponential multiplier
    pub max_attempts: usize,      // how many attempts total (including first)
    pub status_codes: Vec<u16>,   // HTTP codes that trigger a retry
    pub max_delay_secs: Option<u64>, // optional ceiling for back-off
    pub suppress_errors: bool,    // if true, retry errors are not logged
}

```

When Forge initializes, it loads these values from environment variables or uses sensible defaults built into the configuration layer.

## Environment Variables That Control Forge Retry Logic

Forge reads retry parameters from environment variables (or a `.env` file) when constructing the global `ForgeConfig`. These variables override the default values in `RetryConfig`:

- **`FORGE_RETRY_INITIAL_BACKOFF_MS`**: Initial back-off delay in milliseconds before the first retry (default: `1000`)
- **`FORGE_RETRY_BACKOFF_FACTOR`**: Multiplication factor for exponential backoff calculations (default: `2`)
- **`FORGE_RETRY_MAX_ATTEMPTS`**: Maximum number of attempts including the initial call (default: `3`)
- **`FORGE_RETRY_STATUS_CODES`**: Comma-separated list of HTTP status codes that trigger retries (default: `429,500,502,503,504`)
- **`FORGE_SUPPRESS_RETRY_ERRORS`**: When set to `true`, suppresses logging of retry-related error messages (default: `false`)

These variables are documented in the repository README and parsed during the configuration loading phase.

## Implementing Exponential Backoff

The `retry_with_config` function in [`crates/forge_app/src/retry.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_app/src/retry.rs) builds a backon `ExponentialBuilder` using the `RetryConfig` fields:

```rust
let strategy = ExponentialBuilder::default()
    .with_min_delay(Duration::from_millis(config.min_delay_ms))
    .with_factor(config.backoff_factor as f32)
    .with_max_times(config.max_attempts)
    .with_jitter();

let retryable = operation.retry(&strategy).when(should_retry);

```

If a notification callback is supplied, it is invoked on each retry attempt; otherwise the function awaits the retryable future silently.

## Configuring Retries in Practice

Set environment variables in your `.env` file or export them in your shell:

```bash
export FORGE_RETRY_INITIAL_BACKOFF_MS=500
export FORGE_RETRY_BACKOFF_FACTOR=3
export FORGE_RETRY_MAX_ATTEMPTS=5
export FORGE_RETRY_STATUS_CODES=429,500,502,503,504
export FORGE_SUPPRESS_RETRY_ERRORS=true

```

You can verify active settings at runtime through the UI layer in [`crates/forge_main/src/info.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_main/src/info.rs), which renders the "RETRY CONFIGURATION" section during startup.

Example usage with the retry wrapper:

```rust
use forge_app::retry::retry_with_config;
use forge_config::RetryConfig;

let retry_cfg = RetryConfig {
    initial_backoff_ms: 500,
    backoff_factor: 3,
    max_attempts: 5,
    status_codes: vec![429, 500, 502, 503, 504],
    suppress_errors: true,
    ..Default::default()
};

let result = retry_with_config(&retry_cfg, async {
    // Your async API call here
    call_llm_api().await
}, Some(|err, delay| {
    eprintln!("Retrying after {:?} due to: {}", delay, err);
})).await;

```

## Summary

- Forge uses a **two-layer system**: error classification in [`crates/forge_repo/src/provider/retry.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_repo/src/provider/retry.rs) and exponential backoff execution in [`crates/forge_app/src/retry.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_app/src/retry.rs).
- The **`RetryConfig`** struct in [`crates/forge_config/src/retry.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_config/src/retry.rs) defines all tunable parameters including delays, factors, and status codes.
- **Environment variables** like `FORGE_RETRY_MAX_ATTEMPTS` and `FORGE_RETRY_INITIAL_BACKOFF_MS` control runtime behavior without code changes.
- The **backon** crate provides the underlying exponential backoff algorithm with jitter support.
- Provider-specific errors are normalized through the `into_retry` function, ensuring consistent handling across OpenAI, Anthropic, Google, and Bedrock integrations.

## Frequently Asked Questions

### What triggers a retry in Forge?

A retry triggers when the `into_retry` function in [`crates/forge_repo/src/provider/retry.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_repo/src/provider/retry.rs) classifies an error as `DomainError::Retryable`. This occurs when the HTTP status code matches the configured list (429, 500-level errors by default), or when the error represents transport-level failures like timeouts, connection resets, or Anthropic-specific "overloaded" signals.

### How do I increase the number of retry attempts?

Set the **`FORGE_RETRY_MAX_ATTEMPTS`** environment variable to your desired count. This value includes the initial attempt, so setting it to `5` allows for 1 initial call plus 4 retries. The default value is `3` attempts total.

### Can I disable retry error logging?

Yes. Set **`FORGE_SUPPRESS_RETRY_ERRORS=true`** in your environment. When enabled, the `suppress_errors` field in `RetryConfig` prevents retry-related error messages from appearing in logs, though the retries still occur according to the configured strategy.

### Which HTTP status codes are retried by default?

By default, Forge retries the following status codes as specified in `FORGE_RETRY_STATUS_CODES`: **429** (Too Many Requests), **500** (Internal Server Error), **502** (Bad Gateway), **503** (Service Unavailable), and **504** (Gateway Timeout). You can override this list to include additional codes or restrict it to specific error types.