Skip to content

[Bug]: 核心运行时缺陷汇总:后台任务引用被覆盖 / EventBus 静默吞异常 / MCP env·timeout 丢失 #248

Description

@LyaQanYi

概述

对 v2.30.0(1228ac2)做整体代码审查时,在核心运行时发现 6 处缺陷。其中前三条会叠加成一种很难排查的故障形态:机器人静默停止响应,而日志里没有任何线索。

按模块合并提交,如果更希望拆成独立 issue,我可以再分开发。


1. schedule_tasks() 覆盖 self.tasks,后台任务失去强引用且永不被取消(High)

位置:core/lifecycle.py:79-83

async def schedule_tasks(self):
    self.tasks = [
        # asyncio.create_task(self.sticker_manager.scan_and_register_sticker(), name="sticker_scan")
    ]

触发条件:启动即触发,无需特殊配置。

实际行为:执行顺序是 core/lifecycle.py:194 append image_desc_cache_cleanup → :244 append temp_folder_monitor → :252 asyncio.create_task(self.schedule_tasks()),而 schedule_tasks() 一运行就把 self.tasks 重新赋值为空列表,抹掉前两个任务的引用。后果有两个:

  • 事件循环对 task 只持弱引用,失去强引用后这两个后台任务可能在运行中途被 GC 静默回收,临时目录清理与图片描述缓存清理会无声消失。
  • stop()(core/lifecycle.py:293-294)遍历的是空列表,关闭时这两个任务永远不会被取消;AsyncTempMonitor.stop_monitoring() 也从未被任何代码调用。

期望行为:schedule_tasks() 向 self.tasks 追加而非覆盖,使 stop() 能取消全部后台任务。另外 :252 自身也是无引用的 fire-and-forget。


2. EventBus._process_event 吞掉所有 handler 异常且不打任何日志(High)

位置:core/event_bus.py:163-169

if event_type in self.subscribers:
    for handler in tuple(self.subscribers[event_type]):
        try:
            await handler(event)
        except Exception as e:
            self.event_bus_stats["errors"] += 1
            self.stats.set_stats("event_bus", self.event_bus_stats)

实际行为:except 分支只累加计数,不打日志。所有消息处理入口(MessageProcessor.handle_event → handle_im_batch_message → agent 循环)都经由这里调用,任何未在下游捕获的异常都会无声消失。dispatch() 里的 _log_task_error done-callback(:188-198)也永远不会触发,因为异常已经在这里被吞掉了。

这条本身危害有限,但它会把下面第 4、5 条(以及其他任何下游异常)放大成"机器人对所有消息不再回复,日志一片空白"。

期望行为:至少 logger.exception() 记录 handler 异常与事件类型。


3. MCPManager.load_servers() 丢弃 stdio 服务器的 env 与所有服务器的 timeout(High)

位置:core/agent/mcp_mgr.py:142-151

if server_type == "stdio":
    command = server_config.get("command")
    args = server_config.get("args", [])
    server.command = command
    server.args = args
elif server_type in ("sse", "streamable_http"):
    url = server_config.get("url", "")
    headers = server_config.get("headers", {})
    server.url = url
    server.headers = headers

实际行为:从 mcp.json 加载时只读 command/args/url/headers,从不读取 env 和 timeout。MCPServer.env 保持默认空 dict,to_dict()(:55-56 的 if self.env:)随之也不再输出 env。因此依赖环境变量(典型是 API key)的 stdio MCP 服务器每次重启后都会以裸环境启动,工具调用静默失败;配置的 timeout 也会退回默认 10.0 秒。

update_server_from_editor(:388-394)会正常写回这两个字段,说明它们本应被支持,只是初始加载路径漏了。

复现:在 mcp.json 里给某个 stdio 服务器配置 env,重启后该服务器进程拿不到这些环境变量。

期望行为:load_servers() 一并读取 env 与 timeout。


4. max_tool_loop 为 null 时 TypeError 未被捕获(Medium)

位置:core/message_manager.py:642-646

max_tool_loop = self.kira_config.get_config("bot_config.agent.max_tool_loop")
try:
    max_tool_loop = int(max_tool_loop)
except ValueError:
    max_tool_loop = 2

实际行为:只捕获 ValueError。配置里该键为 null(或被删除)时 int(None) 抛 TypeError → handle_im_batch_message 崩溃 → 被上面第 2 条静默吞掉 → 机器人对所有消息停止响应且无日志。

同类逻辑在 core/agent/func_tool_manager.py:66-69 正确捕获了 (TypeError, ValueError),两处不一致。另外 MessageProcessor.__init__(:163-166)的 float(self.bot_config.get("max_message_interval")) 在配置为 null 时会直接让进程启动失败。

期望行为:统一捕获 (TypeError, ValueError)。


5. 工具参数缺失时 raw_args.strip() 抛 AttributeError(Medium)

位置:core/agent/func_tool_manager.py:95-104

raw_args = tool_call.get("function", {}).get("arguments")
try:
    if not raw_args.strip():
        args = {}
    else:
        args = json.loads(raw_args)
except json.JSONDecodeError as e:

实际行为:部分 provider 对无参工具调用会省略 arguments 字段(取回 None),None.strip() 抛 AttributeError,而 except 只捕获 JSONDecodeError。异常沿 execute_tool → agent_executor.run → handle_im_batch_message 传播,该轮对话崩溃且 new_messages 丢失不进记忆(再叠加第 2 条则完全静默)。

期望行为:raw_args 为 None/空时视作 {},并放宽 except 范围。


6. launcher.start() 只捕获取消/中断,启动期异常会跳过全部清理(Medium)

位置:core/launcher.py:90-98

tasks = asyncio.gather(
    self.lifecycle.init_and_run_system(),
    self.webui.run(host, port)
)
await tasks
except (asyncio.CancelledError, KeyboardInterrupt):
    self.logger.info("✨ Exiting KiraAI...")
    await self.lifecycle.stop()

实际行为:若 init_and_run_system() 抛出启动异常(数据库初始化失败、webui.json 解析崩溃等)或 uvicorn 端口占用失败,异常直接穿出 start(),lifecycle.stop() 永远不执行 —— ON_SHUTDOWN 插件钩子、适配器、MCP 持久连接、数据库引擎都得不到有序关闭。且 gather 默认不取消兄弟任务,一边失败后另一边还会继续运行到 asyncio.run 强制拆环。

期望行为:捕获 Exception 并同样走 lifecycle.stop() 清理路径。


补充:测试覆盖

本次审查范围内,core/event_bus.py、core/agent/agent_executor.py(failover 顺序、answered_ids 与 tool_calls 一致性修剪)、core/message_manager.py(_parse_xml_msg、SessionBuffer 语义)、main.py supervisor 的重启退避逻辑均无任何测试。这些大多是分支多、纯逻辑、易单测的代码,且都处在"坏一处就整体失效"的位置。


KiraAI version: v2.30.0(1228ac2)
Environment: 静态代码审查,非特定 OS/Python 版本相关

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions