xai-org/grok-build · error
send_with_retry_escaping_pool ran at least one attempt
Error message
send_with_retry_escaping_pool ran at least one attempt
What it means
send_with_retry_escaping_pool runs a retry loop tracking last_err and at the end returns Err(last_err.expect("send_with_retry_escaping_pool ran at least one attempt")). The expect is an invariant assertion: if the loop exits without any recorded error, zero attempts ran, which the API contract forbids. It panics rather than returning a bogus Ok/Err.
Source
Thrown at crates/codegen/xai-grok-http/src/lib.rs:553
pooled.clone()
}
},
}
} else {
pooled.clone()
};
match op(client).await {
Ok(value) => return Ok(value),
Err(e) if is_retryable(&e) => {
// A silent retry would hide a degrading pool
tracing::debug!(attempt, error = %e, "send_with_retry_escaping_pool: retrying after transient failure");
last_err = Some(e);
}
Err(e) => return Err(e),
}
}
Err(last_err.expect("send_with_retry_escaping_pool ran at least one attempt"))
}
/// Shared blocking client for startup fetches.
/// Carries `STARTUP_FETCH_TIMEOUT` as the connect and read ceiling; do not reuse for long-lived requests.
///
/// This avoids redundant TLS certificate loading for blocking HTTP calls (e.g., model prefetching during startup).
/// The blocking client is separate from the async `shared_client()` because reqwest's blocking client creates its own internal tokio runtime.
///
/// Mirrors `shared_client()`'s pool self-healing for the same reason: this client is reused (settings, prefetch).
/// Idle and TCP eviction drops a connection before the upstream idle window (~60-100s; 30s is a conservative default) closes it.
/// The HTTP/2 keepalive-ping setters that `shared_client()` uses are not exposed on reqwest's blocking `ClientBuilder` (0.12).
/// Only the idle and TCP eviction half applies here.
pub fn shared_startup_blocking_client() -> reqwest::blocking::Client {
static BLOCKING_CLIENT: OnceLock<reqwest::blocking::Client> = OnceLock::new();
BLOCKING_CLIENT
.get_or_init(|| {
let _timer = startup_timer!("startup.http_blocking_client_build");
xai_grok_extra_ca::build_blocking_reqwest_client(|builder| {View on GitHub (pinned to bc7f02eddd)
Solutions
- Clamp the retry policy to at least one attempt before calling (max(1))
- Fix the config/env source so max_attempts is >= 1 and validate it at load time
- If you truly want zero attempts, skip the call instead of invoking the retry wrapper
- Add an assertion/log where the policy is constructed to catch 0-attempt policies early
Example fix
// before
let policy = RetryPolicy { max_attempts: cfg.max_attempts }; // may be 0
send_with_retry_escaping_pool(&req, policy)?;
// after
let policy = RetryPolicy { max_attempts: cfg.max_attempts.max(1) };
send_with_retry_escaping_pool(&req, policy)?; Defensive patterns
Strategy: validation
Validate before calling
assert!(retry_policy.max_attempts >= 1, "retry policy must allow at least one attempt");
let policy = RetryPolicy { max_attempts: retry_policy.max_attempts.max(1) }; Type guard
fn attempts_valid(p: &RetryPolicy) -> bool { p.max_attempts >= 1 } Try / catch
let result = std::panic::catch_unwind(|| send_with_retry_escaping_pool(&req, policy))
.map_err(|_| anyhow!("retry policy yielded zero attempts"))?; Prevention
- Clamp max_attempts to at least 1 where the policy is constructed
- Validate retry config at load time (env/config parsing)
- Never reuse a policy struct meant for a different retry API
- Log the effective attempt count once at startup
When it happens
Trigger: Calling send_with_retry_escaping_pool with a retry policy whose attempt count is 0 (or a config that computes zero attempts), so the loop body never executes and last_err is still None at return.
Common situations: A retry policy loaded from config/env where max_attempts defaults to 0 or parses to 0; an off-by-one in custom retry logic (attempts-1); wiring a policy object intended for a different function.
Related errors
- default reqwest client builds
- failed to build shared HTTP client
- failed to build shared upload HTTP client
- hook HTTP client config is valid
- Failed to parse upload response: {}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/810e8e65415255db.
Report an issue: GitHub.