AI多Agent协作系统实战(十七):凌晨4点,我的AI系统在“假装工作“——3个bug同时爆炸的5小时

系列第17篇 | 一个bug不可怕,三个同时来才致命

凌晨4点的飞书

2026年7月17日,凌晨4点。

我睡不着,打开飞书看了一眼小密(我的AI统筹Agent)的消息。然后我愣住了:

复制代码
【小密统筹报告 07-17 04:04】
【小密统筹报告 07-17 04:34】
【小密统筹报告 07-17 05:04】
【小密统筹报告 07-17 05:34】
【小密统筹报告 07-17 06:04】
【小密统筹报告 07-17 06:34】
【小密统筹报告 07-17 07:04】

7条消息,每条都只有一个标题,没有任何内容。没有任务表格,没有进度,没有状态。

就像一个员工每半小时给你发一封邮件,标题写"工作汇报",打开里面是空白的。

他没在偷懒------他一直在发邮件。但他也没在工作------邮件里什么都没有。

我发了条消息:"有什么问题,一直在空转。"

然后我开始排查,发现不是一个bug,是三个。它们来自三个完全不同的系统层,互相不知道对方的存在,但效果叠加在一起------整个心跳系统"假装工作"了整整5个小时。

第一个炸弹:空报告

现象:心跳每30分钟发一条报告到飞书,从凌晨2点开始正常,4点开始变成只有标题的空消息。

排查过程

我打开format_report.py,看到了这个函数:

python 复制代码
def has_content(notes, timeout_tasks):
    if notes:        # ← 问题在这里
        return True
    if timeout_tasks:
        return True
    return False

看起来没毛病?但问题在于notes里装了什么。

当所有任务都处理完了,脚本会往notes里写一句"无待复核任务"。bool("无待复核任务")返回True,所以has_content说"有内容!",继续生成报告。

但后面有个过滤器:

python 复制代码
filtered_notes = [n for n in notes if not n.startswith("无")]

"无待复核任务"以"无"开头,被过滤掉了。filtered_notes变成空列表。

has_content看的是过滤前的notes ,不是过滤后的filtered_notes

就像你在厨房检查食材有没有过期,但没检查做出来的菜有没有变质。

最终效果:脚本说"有内容"→生成报告→内容被过滤→只剩标题→发送空报告。

修复

python 复制代码
def has_content(notes, timeout_tasks):
    # 不再检查原始notes,检查最终输出
    lines = format_lines(notes, timeout_tasks)
    content_lines = [l for l in lines if l.strip() 
                     and not l.startswith('【') 
                     and not l.startswith('|')]
    return len(content_lines) > 0

教训:判断"有没有内容",要在出口看输出,不要在入口看输入。

第二个炸弹:手机白屏

现象:手机浏览器一片白,控制台没报错,Vue完全没渲染。

排查过程

第一层:资源路径混乱。

mobile/index.html引用了/assets/xxx.js,但文件实际部署在/mobile/assets/下。浏览器请求404,JS不加载,Vue不启动。

但这个修好后,还有第二层------更隐蔽的。

localStorage跨设备污染

我之前在PC端访问系统,点了侧边栏折叠按钮。JS把状态存进localStorage

javascript 复制代码
localStorage.setItem('sidebarCollapsed', 'true')

localStorage按域名存储 的,跟设备无关。同一个域名(nasioe.cc.cd),PC端和手机端共享同一份localStorage

我用手机打开同一个域名,浏览器读到sidebarCollapsed = 'true',JS把侧边栏宽度设成60px。手机屏幕375px宽,60px侧边栏+主内容区,布局全乱。

你以为localStorage是"本地"的,其实它是"本域名"的。

就像你在家里的冰箱贴了一张"今晚吃火锅"的便条,结果你出差住酒店,酒店冰箱里也有一张同样的便条------因为你们用的是同一个美团账号。

修复

javascript 复制代码
// 只在PC端恢复折叠状态
if (window.innerWidth > 768) {
    const saved = localStorage.getItem('sidebarCollapsed');
    if (saved === 'true') {
        sidebar.classList.add('collapsed');
    }
}

第三个炸弹:CSS幽灵

现象:PC端侧边栏折叠按钮被遮挡,折叠后菜单文字没隐藏。

排查过程

我grep了CSS,找到了这段:

css 复制代码
.sidebar.collapsed span:not(.menu-icon) {
    display: none !important;
}

选择器匹配的是span:not(.menu-icon)------也就是隐藏非图标的span

但实际HTML是这样的:

html 复制代码
<a href="/dashboard.html" class="menu-item">
    <span class="menu-icon">📊</span>
    数据看板    <!-- ← 文字直接在<a>里,不在<span>里 -->
</a>

文字不在<span>里,直接写在<a>标签里。CSS选择器根本匹配不到它。

CSS不会报错。它只是静静地失效,看着你的页面一团糟。

就像你给门配了一把钥匙,但门锁已经换了。钥匙插进去,转不动,但门看起来还是那扇门。

修复

css 复制代码
.sidebar.collapsed a {
    font-size: 0;      /* 隐藏<a>内的文字 */
}
.sidebar.collapsed .menu-icon {
    font-size: 18px;   /* 强制显示图标 */
}

三个bug的"完美风暴"

Bug 影响 持续时间
空报告 飞书消息全是空白标题 5小时
手机白屏 手机端完全无法访问 直到修复
CSS幽灵 PC端侧边栏折叠失效 直到修复

三个bug单独看都不致命。但叠加在一起:

  1. 空报告→用户看不到系统在干什么("一直在空转")
  2. 手机白屏→移动端完全不可用
  3. CSS幽灵→PC端UI也有问题

从用户视角看:飞书里一串空消息,手机打不开,电脑侧边栏也是坏的。整个系统似乎瘫痪了。

但实际上,核心逻辑(任务派发、Agent调度、心跳监控)一直在正常运行。出问题的全是展示层

一个"内部正常但外部表现异常"的系统,在用户眼里跟"完全坏掉"没有区别。

三个教训

1. 判断"有没有内容"要看最终输出

has_content检查原始notes,但notes在后面被过滤了。入口看到的和出口输出的不是同一回事。

2. localStorage是域名级别的,不是设备级别的

多设备共用的Web应用,UI状态必须考虑跨设备兼容。用window.innerWidth判断设备类型是最简单的方案。

3. HTML结构改了,CSS选择器必须同步grep

CSS选择器是"盲"的------DOM结构变了,选择器失效,样式静默丢失。改HTML后第一件事:grep所有引用该class的CSS文件。

相关推荐
rannn_1112 小时前
【力扣hot100】链表专题|160、206、234、141、142
java·算法·leetcode·链表·面试·开发
紫禁玄科3 小时前
Shai-Hulud:npm生态的自我复制蠕虫风暴
前端·npm·node.js
东风破_3 小时前
后端API没写好,前端难道干等着吗?
前端
IKUN家族3 小时前
Spring IoC&DI
java·spring·rpc
用户938515635073 小时前
从前后端分离到前端接口工程:React + MockJS + Vite 解析
前端·后端·全栈
用户938515635074 小时前
从零在浏览器里跑 DeepSeek-R1:WebGPU + Transformers.js 全链路实战(三)
前端·react.js·typescript
excel4 小时前
当前端行情变差,我们为什么还要坚持?
前端
鸿是江边鸟,曾是心上人4 小时前
快速搭建HTTPS本地开发环境
前端
山荷枝5 小时前
03-框架--Spring
java·后端·spring
angryshan5 小时前
goLang配置断点流程
go·bug