fix(gateway): bot.toml is read; perm_rx channel stays open

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) <noreply@anthropic.com>
This commit is contained in:
Timmy
2026-05-12 15:28:06 +01:00
parent ddc4228b10
commit 734d3f2eb0
+31 -4
View File
@@ -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(