一封措辞严厉的信:Claude 在硬件在环开发中常犯的错

在 Groundrun,我们经常把 Claude Code 和硬件在环(hardware in the loop)搭配使用。
所谓硬件在环,指的是面向无线嵌入式、联网产品的开发板。涉及的技术和芯片包括 Matter、Thread、蓝牙低功耗(BLE)、Wi-Sun,以及乐鑫 ESP32、Silicon Labs EFR32、Nordic Semiconductor nRF 等。
在用 Claude Code 的过程中,我们见它犯过不少错。大家都知道,AI 编程智能体(coding agent)是会出错的,这没什么大不了,谁都会犯错,我们人类也一样。关键在于,这些错不能一路带进最终产品里。
看到 Claude 反复犯同样的错误之后,我们让它把每一次错误都记录下来,好从中学习。几个月下来,我们攒了几百条这样的问题笔记。下面是其中一些规律:
- Claude 倾向于把问题归咎于硬件
- Claude 常常想改硬件配置,好让测试通过
- 只要某一层能跑通,Claude 就容易以为所有层都正常
- Claude 认为无线连接是 100% 可靠的
- Claude 可能会搞个临时的设备模拟器,还倾向于把它当成硬件的完美复制品
我们能抓到这些错误,是因为我们总是用一套预先定好的测试,把整个系统(固件、应用和云端)完整跑一遍,而且这套测试放在智能体够不着的地方运行。正因为智能体一定会犯错,这道护栏才显得至关重要。
针对编程智能体定下常驻规则,可以缓解上述问题——说白了,就是提醒它们有犯这些特定错误的倾向。这招不一定总灵,但要是没有这些规则,Claude 往往会一遍又一遍地犯同样的错。

硬件在环(Hardware in the loop):把无线开发板、一部手机和一个编程智能体连起来(左图),现实中桌面上可能乱成一团(右图)。
这意味着,我们绝对需要在真实硬件上、按照用户实际使用产品的方式来验证编程智能体的工作。当然,过去人类写代码时也一直如此,但智能体速度更快,犯错也更快。而且它们说话时信心十足,所以我们人类很容易相信它们告诉我们的东西。
Claude 把锅甩给硬件
这种情况经常发生:编程智能体写了一段代码,看起来没问题,但在硬件上跑起来就是不对劲,于是 Claude 倾向于怪硬件。或者说,怪任何不属于 Claude 所写代码的系统部分。最后它会说 USB 口大概接触不良,或者射频芯片工作不正常,又或者厂商的 SDK 功能不完整。
一封措辞强硬的信。有一次通宵干活,Claude 甚至到了起草一封信给硬件厂商的地步,信中解释了它认为的硬件缺陷。这些所谓的“硬件缺陷”,后来发现其实是 Claude 自己犯的配置错误。
怪硬件,再给测试加个重试。在一个用到边界路由板(border router board)的项目里,最初所有测试运行中有 83%–91% 都失败了,Claude 断定射频板本身不稳定,想在测试里加一套重试机制来绕开这种不稳定。结果根因是串口链路被误配置成了硬件流控,而 USB 适配器上的硬件流控却没有开启。一旦把它打开,就再也没有失败过。
现在给智能体定下的硬性规则是:凡是看起来像硬件故障或厂商缺陷的问题,一律当作我们这边的 Bug——只是碰巧被硬件暴露了出来。
Claude 想改设备好让测试通过
测试跑不起来时,Claude 的本能是想尽办法让它快点跑起来,通常是想重新配置硬件来迁就测试。有时候它还会倾向于去改动最终用户的应用。
放慢串口速度。有个项目里,一块硬件板卡通过串口线连接到 Groundrun 运行器,串口线上跑着一个 Python 脚本,负责来回收发测试数据包。这条串口线本应以 460800 bps 运行,但 Claude 第一版 Python 脚本速度跟不上。于是它第一反应是把硬件改成更慢的 115200 bps,这样初步测试就能通过。可这条串口链路本来就需要更快,到了下一阶段照样会失败——这时候 Claude 才需要用正确的方式解决:重写 Python 脚本,换用更快的机制。
拆掉安全层。有个应用需要和蓝牙传感器通信,得先跟蓝牙设备配对。这意味着 Claude 要多做一步应用自动化,在手机上点中正确的配对按钮。但这次 Claude 同时能改应用和蓝牙固件代码,它选择直接改固件,把蓝牙连接的配对安全关掉。测试一开始是能跑通了,但这显然不是正确的解决方式。其他测试很快发现了问题,Claude 这才改回在应用里补上正确的点击操作。
这些都是严重问题,说明必须在智能体自己的编码循环之外,做完整的产品测试。
Claude 把无线电链路当成可靠管道
这个问题很棘手,连我们人类也容易犯:在实验室环境里,大多数无线电链路都是 100% 可靠的,一个数据包都不会丢。但一旦部署到现场,设备之间距离拉大,无线电链路就不再完美了。
Claude 往往会把无线电链路当成完美链路,也许是因为它训练时接触的其他网络连接通常更可靠。
十次测试全部干净通过,但实际上根本没跑通。在最近一篇文章里,智能体把蓝牙固件更新从 8 分钟缩短到了 22 秒。听起来很棒!十次测试全部通过。但这些测试是在实验室条件下跑的,无线电链路完美无缺。可等到最终测试用上了损耗更大的链路,应用就跑不起来了。Claude 得知这一点后,确实能重新设计机制,但如果当初放手让它干,它早就一头扎进坑里了。
一层通了,Claude 就以为层层都通
一个联网产品是分层搭起来的:芯片、引导程序、固件、无线电协议栈、网络,最后是手机上的应用。每一层都可能看起来健康,而别处已经坏了。Claude 往往一看到还有生命迹象,就当成整个栈都正常。
这个错误连人类都容易犯。
连上了,但其实是孤岛。 一块板子本该加入一个已有的 Thread 网络。固件打印出“已连接”,三次运行三次如此。测试就等着这行输出,于是通过了。可板子压根没加入网络。它没找到目标网络,放弃了,然后自己组了一个新网络,成员只有它自己。原因是应用没提供网络凭证。Claude 最终能查出来,但需要人工干预,因为它满足于“板子加入了一个网络”这个结论。
跑的是厂商示例,不是我们的固件。 一个 Matter 固件构建编译、烧录、启动,显示了配网二维码,还通过蓝牙广播自己。一切看起来都正常。但构建过程中的一个复制步骤悄悄保留了厂商生成的文件,而不是实际代码,所以板子上跑的是芯片厂商的库存示例。
这类故障模式只能靠端到端测试来抓:用户在应用里点了按钮,灯到底亮没亮?没亮,就继续查。
Claude 可能自己写一个设备模拟器,然后把它当成硬件的完美复刻
模拟器很有用。它比真硬件快得多,还能并行跑很多个。但模拟器只是硬件的简化模型,简化有时恰恰会把真正的 bug 藏起来。一旦有了模拟器,Claude 往往就把它当成真硬件来信任——哪怕是 Claude 自己写出来的模拟器。
一次只有一个写入在途。 在蓝牙固件更新那篇文章里,Claude 意识到应该给蓝牙链路写一个一次性模拟器,方便快速迭代。但模拟器没有复现真实蓝牙协议栈同时在途的数据包数量,结果应用最后并没有解决真正的问题。
快速迭代时,模拟器仍然是好工具,但结果要尽快拿到真硬件上验证。这样 Claude 才能及时纠正自己。
如何应对这些错误
既然知道 Claude 会犯错,我们该拿这个认知怎么办?
第一,所有东西都要有测试,而且测试要独立于写代码的 agent。测试必须放在 agent 够不着的地方,这样它既改不了测试,也没法糊弄过去让测试变绿。
第二,我们可以帮 Claude 做对的事,办法是让它知道自己容易犯哪些错。最简单的起步方式,就是让它维护一份常见错误清单,干活时对照着想一想。项目更复杂的话,可以维护一个数据库,让 Claude 工作时随时查阅。
但这里真正的启发不是「Claude 会犯错」——它当然会,人也一样。真正的启发是:代码必须被问责,必须在真实硬件上证明自己。
Groundrun 平台概览 →