From d548caa5f3f7d6d53fab5978ee44b8fca61e17ea Mon Sep 17 00:00:00 2001 From: Chummy Date: Thu, 19 Feb 2026 14:15:39 +0800 Subject: [PATCH] fix(channel): clamp configurable timeout to minimum 30s --- docs/config-reference.md | 1 + src/channels/mod.rs | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/config-reference.md b/docs/config-reference.md index cfd65fb..725993c 100644 --- a/docs/config-reference.md +++ b/docs/config-reference.md @@ -65,6 +65,7 @@ Notes: - Default `300s` is optimized for on-device LLMs (Ollama) which are slower than cloud APIs. - If using cloud APIs (OpenAI, Anthropic, etc.), you can reduce this to `60` or lower. +- Values below `30` are clamped to `30` to avoid immediate timeout churn. - When a timeout occurs, users receive: `⚠️ Request timed out while waiting for the model. Please try again.` See detailed channel matrix and allowlist behavior in [channels-reference.md](channels-reference.md). diff --git a/src/channels/mod.rs b/src/channels/mod.rs index 67654a7..75544f4 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -60,6 +60,7 @@ const BOOTSTRAP_MAX_CHARS: usize = 20_000; const DEFAULT_CHANNEL_INITIAL_BACKOFF_SECS: u64 = 2; const DEFAULT_CHANNEL_MAX_BACKOFF_SECS: u64 = 60; +const MIN_CHANNEL_MESSAGE_TIMEOUT_SECS: u64 = 30; /// Default timeout for processing a single channel message (LLM + tools). /// Used as fallback when not configured in channels_config.message_timeout_secs. const CHANNEL_MESSAGE_TIMEOUT_SECS: u64 = 300; @@ -73,6 +74,10 @@ const MODEL_CACHE_PREVIEW_LIMIT: usize = 10; type ProviderCacheMap = Arc>>>; type RouteSelectionMap = Arc>>; +fn effective_channel_message_timeout_secs(configured: u64) -> u64 { + configured.max(MIN_CHANNEL_MESSAGE_TIMEOUT_SECS) +} + #[derive(Debug, Clone, PartialEq, Eq)] struct ChannelRouteSelection { provider: String, @@ -1811,6 +1816,8 @@ pub async fn start_channels(config: Config) -> Result<()> { let mut provider_cache_seed: HashMap> = HashMap::new(); provider_cache_seed.insert(provider_name.clone(), Arc::clone(&provider)); + let message_timeout_secs = + effective_channel_message_timeout_secs(config.channels_config.message_timeout_secs); let runtime_ctx = Arc::new(ChannelRuntimeContext { channels_by_name, @@ -1833,7 +1840,7 @@ pub async fn start_channels(config: Config) -> Result<()> { reliability: Arc::new(config.reliability.clone()), provider_runtime_options, workspace_dir: Arc::new(config.workspace_dir.clone()), - message_timeout_secs: config.channels_config.message_timeout_secs, + message_timeout_secs, }); run_message_dispatch_loop(rx, runtime_ctx, max_in_flight_messages).await; @@ -1879,6 +1886,19 @@ mod tests { tmp } + #[test] + fn effective_channel_message_timeout_secs_clamps_to_minimum() { + assert_eq!( + effective_channel_message_timeout_secs(0), + MIN_CHANNEL_MESSAGE_TIMEOUT_SECS + ); + assert_eq!( + effective_channel_message_timeout_secs(15), + MIN_CHANNEL_MESSAGE_TIMEOUT_SECS + ); + assert_eq!(effective_channel_message_timeout_secs(300), 300); + } + #[derive(Default)] struct RecordingChannel { sent_messages: tokio::sync::Mutex>,