From 734d3f2eb06da184260f5a00601d671da122c59a Mon Sep 17 00:00:00 2001 From: Timmy Date: Tue, 12 May 2026 15:28:06 +0100 Subject: [PATCH] fix(gateway): bot.toml is read; perm_rx channel stays open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two latent bugs in `service/gateway/io.rs::spawn_gateway_bot`, exposed today after a long-overdue gateway rebuild: 1. The permission channel sender was bound as `_perm_tx` (the underscore prefix signalling "unused") and dropped at function return. The matrix bot's permission_listener task — which holds `perm_rx` for its lifetime per story 884 — then saw the channel close immediately and exited with "perm_rx channel closed" 1s after starting. Net effect: the listener was effectively absent on every gateway boot, so non-MCP tool permission requests had no destination at all (separate from the architectural mismatch that 898 will fix; this was a strictly worse "listener never even ran" version of the same problem). Bind as `perm_tx` and `mem::forget` it to keep the channel open for the gateway's lifetime, mirroring the existing `shutdown_tx` pattern two lines below. 2. `bot_name` was hardcoded to `"Assistant"`, ignoring `bot.toml::display_name`. So the gateway's matrix bot announced itself as "Assistant" and treated user messages addressed to "Timmy" (the actual configured display_name) as unaddressed, silently dropping them. `ambient_rooms` and `permission_timeout_secs` were similarly ignored. Load `BotConfig::load(config_dir)` and apply the same field plumbing the standard-mode initialisation in `main.rs:211-232` already uses. Symptoms seen in production today: - gateway.log: `Sending startup announcement: Assistant is online.` followed by repeated `Ignoring unaddressed message from @yossarian:crashlabs.io` lines. - gateway.log: `permission listener started` immediately followed (same timestamp) by `permission listener exiting (perm_rx channel closed)`. After this lands, rebuild the gateway binary and restart so it picks up `bot.toml` correctly and the listener stays alive for the bot's lifetime. Co-Authored-By: Claude Opus 4.7 (1M context) --- server/src/service/gateway/io.rs | 35 ++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/server/src/service/gateway/io.rs b/server/src/service/gateway/io.rs index 8eaae0e8..f1f2b08f 100644 --- a/server/src/service/gateway/io.rs +++ b/server/src/service/gateway/io.rs @@ -422,7 +422,16 @@ pub fn spawn_gateway_bot( use tokio::sync::{broadcast, mpsc}; let (watcher_tx, _) = broadcast::channel(16); - let (_perm_tx, perm_rx) = mpsc::unbounded_channel(); + let (perm_tx, perm_rx) = mpsc::unbounded_channel(); + // Keep the sender alive for the gateway's lifetime so the matrix bot's + // `permission_listener` task doesn't exit immediately with + // "perm_rx channel closed". Previously `_perm_tx` was dropped when + // `spawn_gateway_bot` returned, closing the channel before the + // listener could even register. Story 898 (sled→gateway WS uplink) + // will eventually wire in a real sender; for now the leak keeps the + // channel open with no senders writing to it, matching the original + // intent of "listener watches forever, waiting for requests". + std::mem::forget(perm_tx); let perm_rx = std::sync::Arc::new(tokio::sync::Mutex::new(perm_rx)); let (shutdown_tx, shutdown_rx) = @@ -431,18 +440,36 @@ pub fn spawn_gateway_bot( let agents = std::sync::Arc::new(AgentPool::new(port, watcher_tx.clone())); + // Read the gateway's bot.toml so display_name, ambient_rooms, and + // permission_timeout_secs are honoured (matches the standard-mode + // initialisation in main.rs). Previously this path hardcoded + // `bot_name = "Assistant"` regardless of bot.toml's display_name, + // breaking @-addressing for users who configured a different name. + let bot_cfg = crate::chat::transport::matrix::BotConfig::load(config_dir); + let services = std::sync::Arc::new(Services { project_root: config_dir.to_path_buf(), status: agents.status_broadcaster(), agents, - bot_name: "Assistant".to_string(), + bot_name: bot_cfg + .as_ref() + .and_then(|c| c.display_name.clone()) + .unwrap_or_else(|| "Assistant".to_string()), bot_user_id: String::new(), - ambient_rooms: std::sync::Arc::new(std::sync::Mutex::new(std::collections::HashSet::new())), + ambient_rooms: std::sync::Arc::new(std::sync::Mutex::new( + bot_cfg + .as_ref() + .map(|c| c.ambient_rooms.iter().cloned().collect()) + .unwrap_or_default(), + )), perm_rx, pending_perm_replies: std::sync::Arc::new(tokio::sync::Mutex::new( std::collections::HashMap::new(), )), - permission_timeout_secs: 120, + permission_timeout_secs: bot_cfg + .as_ref() + .map(|c| c.permission_timeout_secs) + .unwrap_or(120), }); let timer_store = std::sync::Arc::new(crate::service::timer::TimerStore::load(