概述
对 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 版本相关
概述
对
v2.30.0(1228ac2)做整体代码审查时,在核心运行时发现 6 处缺陷。其中前三条会叠加成一种很难排查的故障形态:机器人静默停止响应,而日志里没有任何线索。按模块合并提交,如果更希望拆成独立 issue,我可以再分开发。
1.
schedule_tasks()覆盖self.tasks,后台任务失去强引用且永不被取消(High)位置:
core/lifecycle.py:79-83触发条件:启动即触发,无需特殊配置。
实际行为:执行顺序是
core/lifecycle.py:194appendimage_desc_cache_cleanup→:244appendtemp_folder_monitor→:252asyncio.create_task(self.schedule_tasks()),而schedule_tasks()一运行就把self.tasks重新赋值为空列表,抹掉前两个任务的引用。后果有两个: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实际行为:
except分支只累加计数,不打日志。所有消息处理入口(MessageProcessor.handle_event→handle_im_batch_message→ agent 循环)都经由这里调用,任何未在下游捕获的异常都会无声消失。dispatch()里的_log_task_errordone-callback(:188-198)也永远不会触发,因为异常已经在这里被吞掉了。这条本身危害有限,但它会把下面第 4、5 条(以及其他任何下游异常)放大成"机器人对所有消息不再回复,日志一片空白"。
期望行为:至少
logger.exception()记录 handler 异常与事件类型。3.
MCPManager.load_servers()丢弃 stdio 服务器的env与所有服务器的timeout(High)位置:
core/agent/mcp_mgr.py:142-151实际行为:从
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实际行为:只捕获
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实际行为:部分 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实际行为:若
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.pysupervisor 的重启退避逻辑均无任何测试。这些大多是分支多、纯逻辑、易单测的代码,且都处在"坏一处就整体失效"的位置。KiraAI version:
v2.30.0(1228ac2)Environment: 静态代码审查,非特定 OS/Python 版本相关