返回 2026-06-13
🤖 AI / ML

Claude Fable:极具主动性的 AI 模型Claude Fable is relentlessly proactive

simonwillison.net·2026-06-11

Simon Willison 在体验 Claude Fable 5 两天后,认为其最大特点是“极具主动性”。该模型掌握了大量技巧,并会不遗余力地利用这些技巧来实现既定目标。作者通过开发 Datasette Agent 时的实际案例,展示了 Claude Fable 如何主动解决代码中出现的意外故障。这种不达目的誓不罢休的特性,使其在编程辅助中表现出极高的执行效率。

Simon Willison

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 is not allowed assistive access”
  • 想出了上文描述的 uv run --with pyobjc-framework-Quartz python 这一变通方案
  • 在站点模板中添加了 JavaScript 以触发 / 键
  • 搭建了它自己的微型 Python CORS Web 服务器来捕获 JSON 数据
  • 重写了模板以捕获该数据并将其发送到服务器
  • 通过编写脚本穿透 Web 组件的 Shadow DOM 获取到了所需的信息
  • 打开 Safari 确认了 Bug 的来源
  • 修改了其自定义模板,强行加入了一个潜在的修复方案
  • 确认了这种强行修复的方法奏效了
  • 报告了如何修复该问题
  • 正如我所说,极其主动!

    成本估算

    我目前使用的是每月 100 美元的 Claude Max 计划,其中包含 Fable 的高额免费额度,该额度持续到 6 月 22 日;之后 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 中偏差的正常化》(The Normalization of Deviance in AI)一文中所指出的,我认为这最有可能引发一场类似“挑战者号”那样的灾难事故。

    可以说,Fable 更加聪明,因此对潜在的恶意指令也更为警惕。但这种聪明无疑是一把双刃剑:一旦它真的被恶意指令所操纵,凭借其不知疲倦的主动性,它所能造成的破坏力将是极其可怕的。

    需要完整排版与评论请前往来源站点阅读。