Simon Willison 的个人博客
Claude Fable 具有极强的主动性
2026 年 6 月 11 日
经过两天对 Claude Fable 5 的使用体验,我认为最好的描述方式是“具有极强的主动性”。它掌握大量技巧,并且会动用几乎任何手段来达成目标。
我举个例子来说明。今天我在开发 Datasette Agent 时注意到一个界面缺陷:跳转菜单的聊天提示框中出现了一个不该存在的水平滚动条。我截下了这张截图:
然后我在 datasette-agent 的代码目录下启动了一个新的 claude 会话,把截图拖进去并告诉它:
查看依赖项,帮助找出这里出现水平滚动条的原因
我隐约感觉问题出在 Datasette Agent 的某个依赖项(很可能就是 Datasette 本身)上,而且我知道 Fable 擅长深入分析依赖代码——无论是检查自己虚拟环境 site-packages 中已安装的文件,还是引用磁盘上的本地代码仓库。让它从依赖项入手,感觉是个不错的策略。
我因为家务事分心,离开了电脑。
几分钟后我回来时,看到我的电脑在常规的 Firefox 浏览器中打开了一个窗口,然后导航到了那个有问题的对话框。我并没有指示 Claude Code 使用任何浏览器自动化功能,而且我很确定它不可能在窗口内触发鼠标移动或键盘快捷键,那它是怎么做到的呢?
我饶有兴致地看着它继续探索,接着看到它打开了 Safari 浏览器窗口,而不是 Firefox。我还从 Claude 终端抓取了这张截图:
它用 `uv run --with pyobjc-framework-Quartz` 在做什么?
原来 Fable 自己编写了一套截取浏览器窗口截图的方案。它用 Python 遍历我电脑上所有可用的窗口,然后筛选出窗口名称中包含“textarea”等预期字符串的 Safari 窗口。它利用这些信息找到窗口编号——一个像 153551 这样的整数——然后配合 screencapture 命令行工具来抓取 PNG 图片。
好吧,这确实是一种巧妙的截图方式。但它到底在截取什么内容的截图呢?
结果发现,它一直在自行编写临场 HTML 页面来尝试复现那个 bug,然后打开 Safari 并截取屏幕截图。
这是它创建的 `/tmp/textarea-scrollbar-test.html` 页面,以及它用 `screencapture -x -o -l 153551 /tmp/safari-cases.png` 命令截取的截图:
(我打开的标签页实在太多了!)
好吧,我明白它是如何打开测试页面并截取屏幕截图的了,但它究竟是怎么触发那个本应处于测试状态的模态对话框的呢?那个对话框只能通过点击或键盘快捷键来调用,而我找不到它在 Safari 中执行这些操作的方法。
我最终弄明白了它的做法。
Claude 运行在一个包含该应用程序源代码的文件夹中。它对 Datasette 足够了解,能够运行一个本地开发服务器。结果发现,它正在编辑 Datasette 自身的模板,添加 JavaScript 代码,以便在窗口打开时立即触发正确的键盘快捷键,添加的代码如下:
<script> window.addEventListener("load", function () { setTimeout(function () { document.dispatchEvent(new KeyboardEvent("keydown", {key: "/", bubbles: true})); }, 1200); }); </script>
窗口打开 1.2 秒后,这段代码会触发一个模拟的 `/` 按键,这是打开模态对话框的键盘快捷键。
还剩一个挑战。为了理解当前状况,Claude 需要在页面上运行 JavaScript 来自行获取测量数据。
它编写了自己的自定义 Web 应用程序,通过 CORS 来捕获信息,然后将其作为本地服务器运行,并打开一个包含 JavaScript 的页面,该页面会直接向它发送 POST 请求!
这是它编写的 Python Web 应用程序,使用了标准库中的 `http.server` 包:
from http.server import HTTPServer, BaseHTTPRequestHandler class H(BaseHTTPRequestHandler): def do_POST(self): n = int(self.headers.get("Content-Length", 0)) open("/tmp/diag.json", "w").write(self.rfile.read(n).decode()) self.send_response(200) self.send_header("Access-Control-Allow-Origin", "*") self.end_headers() def do_OPTIONS(self): self.send_response(200) self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Headers", "*") self.end_headers() def log_message(self, *a): # quiet pass HTTPServer(("127.0.0.1", 9999), H).serve_forever()
这段代码所做的就是接受一个包含 JSON 数据的 POST 请求,并将其写入 `/tmp/diag.json` 文件。它会发送 `Access-Control-Allow-Origin: *` 头(包括来自 OPTIONS 请求的),以便运行在其他域名上的代码仍然可以与之通信。
然后,Claude 将这段代码注入到它正在浏览器中加载的模板里:
const host = document.querySelector("navigation-search"); const ta = host.shadowRoot.querySelector("textarea"); const cs = getComputedStyle(ta); fetch("http://127.0.0.1:9999/diag", { method: "POST", body: JSON.stringify({ dpr: window.devicePixelRatio, scrollWidth: ta.scrollWidth, clientWidth: ta.clientWidth, whiteSpace: cs.whiteSpace, width: cs.width, }), });
这段代码测量了 `<navigation-search>` Web 组件内部的 `<textarea>`,并将测量结果发送到服务器,服务器将其写入磁盘上的一个文件,Claude 随后便可以读取该文件。
在掌握了所有这些技巧之后,Fable……撞上了某个无形的护栏,把自己降级成了 Opus。幸运的是,Opus 能访问完整的对话记录,可以继续使用 Fable 开创的那些技巧,并且很快找到了、测试并验证了修复方案。
我提示 Opus 执行以下操作:
在 /tmp/automation-report.md 中写一份报告,记录你在此次会话中用于在我电脑上测试真实浏览器的所有技巧,并包含可运行的代码示例
它生成了这份报告,对于拼凑出这篇文章中发生的细节来说,这份报告价值连城。
我也分享了 Claude Code 会话的完整终端记录。
对其所做一切工作的回顾
基于一张截图和一行提示词,Claude Fable 5 + Claude Code:
- 弄清楚了运行本地开发服务器的方案(包括启动它所需的虚假环境变量)
- 启动了一个 Playwright Chrome 会话
- 开启了 Chrome 的可见滚动条设置 `defaults write com.google.chrome.for.testing AppleShowScrollBars Always`(之后它又关闭了这个设置)
- 也在 Playwright 中轮换测试了 Firefox 和 WebKit,未能重现该 bug
- 推断出我的默认浏览器是 Safari
- 构建了一个 textarea-scrollbar-test.html 的 HTML 文档
- 在真实的(非 Playwright 的)Firefox 中打开了该文档
- 发现 `osascript -e 'tell application "System Events" to tell process "firefox" to id of window 1'` 被阻止,因为“osascript 未获得辅助功能访问权限”
- 想出了上面描述的 `uv run --with pyobjc-framework-Quartz python` 变通方案
- 向网站模板中添加了 JavaScript,以便触发 / 键
- 构建了自己的小型 Python CORS Web 服务器来捕获 JSON 数据
- 重写了模板以捕获该数据并将其发送到服务器
- 通过 Web Component 的 shadow DOM 编写脚本,找到了它所需的信息
- 打开 Safari 以确认 bug 的来源
- 修改了它的自定义模板,尝试植入一个潜在的修复方案
- 确认了该临时修复方案有效
- 报告了如何修复该问题
就像我说的,极其主动!
成本估算
我目前使用的是每月 100 美元的 Claude Max 套餐,其中包含到 6 月 22 日为止 Fable 的慷慨用量额度,之后 Anthropic 表示将开始按完整 API 价格收费。
我正在用 AgentsView 追踪我的花费(参见这篇 TIL)。以下是 AgentsView 显示这次会话如果按全价付费所需的费用:
~ % uvx agentsview session usage be8850a7-6119-46a0-b5d6-79c7fff5ae2b
Session: be8850a7-6119-46a0-b5d6-79c7fff5ae2b
Agent: claude
Output: 68606
Peak ctx: 113178
Cost: ~$12.11 (claude-fable-5, claude-opus-4-8)
如果你不盯紧点,Fable 会很乐意烧掉价值 12 美元的模型 token,来发明调试 CSS 的新方法。
我真的得把这东西锁死。
一方面,看着 Fable 不遗余力地去获取它所需的信息,来调试一个最终只需两行 CSS 就能修复的问题,确实很吸引人。
但另一方面……这有力地提醒了我们:编程智能体可以做到任何你通过在终端输入命令能做的事情——而且前沿模型通晓所有已知技巧,显然还掌握了一些前人从未记录过的方法。
如果 Fable 当时是在执行恶意指令——比如隐藏在代码或 issue 线程中的提示词注入攻击,或者我不小心粘贴到终端里的内容——它能走多远来窃取数据或造成其他破坏,想想都令人不安。
在沙箱之外运行编程智能体一直是个坏主意——我认为它最有可能成为 Johann Rehberger 在《AI 中的偏差常态化》中所描述的“挑战者号灾难”式事件。
可以说 Fable 更聪明,因此对潜在恶意指令也更警惕。但这种聪明是一把双刃剑:如果它真的被指令所颠覆,考虑到它那 relentless 的主动性,它能造成的破坏程度是可怕的。
2026 年 6 月 11 日
更多近期文章
- Datasette Apps:在 Datasette 内部托管自定义 HTML 应用程序 - 2026 年 6 月 18 日
- GLM-5.2 可能是目前最强大的纯文本开源权重大语言模型 - 2026 年 6 月 17 日
- 将 WASM wheels 发布到 PyPI 以供 Pyodide 使用 - 2026 年 6 月 13 日
本文是《Claude Fable 具有 relentless 的主动性》,作者 Simon Willison,发布于 2026 年 6 月 11 日。
claude-mythos