From ae1f13402b233a2be564917a7a1da839dff3f4c8 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 8 Mar 2026 10:03:50 -0400 Subject: [PATCH] Add 5-minute stream read timeout and increase retry backoff to 5s A stalled Anthropic SSE stream caused a run to hang for 10 minutes until the stall watchdog killed it. The HTTP connection succeeded but no SSE events arrived, and with stream_read_timeout defaulting to None the read blocked indefinitely. - Set AdapterTimeout default stream_read to 300s (5 min), matching the idle timeout used by OpenAI Codex - Increase BackoffConfig default initial_delay_ms from 200ms to 5s so transient LLM failures get meaningful recovery time before retry Co-Authored-By: Claude Opus 4.6 --- crates/arc-llm/src/types.rs | 4 ++-- crates/arc-workflows/src/engine.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/arc-llm/src/types.rs b/crates/arc-llm/src/types.rs index e7438e878..4a6a81ca7 100644 --- a/crates/arc-llm/src/types.rs +++ b/crates/arc-llm/src/types.rs @@ -703,7 +703,7 @@ impl Default for AdapterTimeout { Self { connect: 30.0, request: None, - stream_read: None, + stream_read: Some(300.0), } } } @@ -1194,7 +1194,7 @@ mod tests { let timeout = AdapterTimeout::default(); assert!((timeout.connect - 30.0).abs() < f64::EPSILON); assert!(timeout.request.is_none()); - assert!(timeout.stream_read.is_none()); + assert!((timeout.stream_read.unwrap() - 300.0).abs() < f64::EPSILON); } #[test] diff --git a/crates/arc-workflows/src/engine.rs b/crates/arc-workflows/src/engine.rs index a8fcc0b36..a994e7e7d 100644 --- a/crates/arc-workflows/src/engine.rs +++ b/crates/arc-workflows/src/engine.rs @@ -71,7 +71,7 @@ pub struct BackoffConfig { impl Default for BackoffConfig { fn default() -> Self { Self { - initial_delay_ms: 200, + initial_delay_ms: 5_000, backoff_factor: 2.0, max_delay_ms: 60_000, jitter: true, @@ -125,13 +125,13 @@ impl RetryPolicy { } } - /// Standard retry policy: 5 attempts, 200ms initial, 2x factor. + /// Standard retry policy: 5 attempts, 5s initial, 2x factor. #[must_use] pub const fn standard() -> Self { Self { max_attempts: 5, backoff: BackoffConfig { - initial_delay_ms: 200, + initial_delay_ms: 5_000, backoff_factor: 2.0, max_delay_ms: 60_000, jitter: true, @@ -2322,7 +2322,7 @@ mod tests { fn retry_policy_standard() { let policy = RetryPolicy::standard(); assert_eq!(policy.max_attempts, 5); - assert_eq!(policy.backoff.initial_delay_ms, 200); + assert_eq!(policy.backoff.initial_delay_ms, 5_000); } #[test] @@ -2399,7 +2399,7 @@ mod tests { let policy = build_retry_policy(&node, &graph); assert_eq!(policy.max_attempts, 4); // 3 retries + 1 initial // Should use default backoff, not a preset's backoff - assert_eq!(policy.backoff.initial_delay_ms, 200); + assert_eq!(policy.backoff.initial_delay_ms, 5_000); } #[test]