Simon Willison · 博客

sqlite-utils 4.0rc2,主要由 Claude Fable 编写(花费约 149.25 美元)

sqlite-utils 4.0rc2, mostly written by Claude Fable (for about $149.25)

二〇二六年七月五日 · 英文原文

sqlite-utils 4.0rc2 发布,由 Claude Fable 辅助完成最终审查与修复。Fable 发现 `delete_where()` 存在数据丢失 bug,因未使用 `atomic()` 包装导致事务未提交。经 37 个 prompt、34 次提交、+1,321/-190 行变更后修复。GPT-5.5 进一步审查发现 `db.query()` 在非行语句前自动提交写入及 `INSERT ... RETURNING` 未迭代时不提交的问题。总成本 $149.25。新版本引入自动提交事务模型,并新增 `db.begin()`、`db.commit()`、`db.rollback()` 方法。

几周前我写了关于 sqlite-utils 4.0rc1 发布的文章。由于我们的 Max 订阅中 Claude Fable 的使用权限仅剩几天,我决定看看它能否帮助我发布一个真正让我放心的 4.0 稳定版——因为我尽量遵循 SemVer,并且希望不兼容的主版本越少越好。我在 iPhone 上通过 Claude Code for web 以这个 prompt 开始:

在发布稳定版 4.0 之前的最终审查——非常重要,要发现任何如果以后修复会变成 breaking change 的最后一刻问题。

这是它为我生成的初始报告。其中有一些我自己尚未遇到的重大问题——Fable 将其中的 5 个归类为"发布阻塞器"。以下是其中最严重的一个:

  1. delete_where() 从不提交并污染连接(数据丢失) Table.delete_where() (sqlite_utils/db.py:2948) 通过裸的 self.db.execute() 执行 DELETE,没有 atomic() 包装——对比 db.py:2944Table.delete(),后者正确包装了。连接保持 in_transaction=True,因此后续每次 atomic() 调用都会走 savepoint 分支 (db.py:430-440),也从不提交。端到端复现:
    db = sqlite_utils.Database("dw.db")
    db["t"].insert_all([{"id": i} for i in range(3)], pk="id")
    db["t"].delete_where("id = ?", [0])
    # conn.in_transaction 现在为 True
    db["t"].insert({"id": 50})
    db["u"].insert({"a": 1})
    db.close()
    # 重新打开:rows 为 [0, 1, 2]——delete、row 50 以及表 u 全部丢失。
    
    这是一个非常严重的 bug!很高兴我没有发布这个版本,尽管至少它是一个可以在 4.0.1 补丁版本中修复的 bug,而不是一个会迫使升级到 5.0 的设计缺陷。

经过 37 个 prompt、34 次提交以及跨越 30 个独立文件的 +1,321/-190 代码变更,我们依次处理了全部反馈,并在此过程中做了若干其他设计改进。编码 agent 的一个奇怪之处在于,像这样更困难的任务实际上提供了更多同时做其他事情的机会,因为 agent 有时需要 10-15 分钟来处理新任务。我出去享受了半月湾的国庆游行,偶尔用手机查看进度并给 Fable 下达下一步指令。详细信息见 PR 和这份共享对话记录。

我在笔记本电脑上通过 GitHub 的 PR 界面进行了最终审查。最重要的变更与事务处理有关,这是之前 RC 中标志性的新特性。新的 RC 现在包含关于新事务模型的全面文档,其引言我在此完整引用:

本库中每个写入数据库的方法——insert()upsert()update()delete()delete_where()transform()create_table()create_index()enable_fts() 等——都在自己的事务内运行,并在返回前提交。方法调用完成后,你的更改就已保存到磁盘:

db = Database("data.db")
db.table("news").insert({"headline": "Dog wins award"})
# 新行已保存——无需 commit()

同样的规则适用于通过 db.execute() 执行的原始 SQL——写入语句运行后立即提交。你永远不需要调用 commit(),也不需要关闭数据库来持久化更改。 只有两种情况下你需要考虑事务:

  • 你想将多个写操作分组,使它们要么全部成功要么全部失败——使用 db.atomic()
  • 你通过 db.begin() 自行管理事务,在这种情况下,直到你提交之前什么都不会提交——本库永远不会提交你打开的事务。

在审查 Fable 的文档时——我发现先审查文档编辑是建立对变更初步理解的绝佳方式——我注意到了这个细节:

db.atomic() 和每个方法的自动事务是为 Python 默认事务处理模式下的连接设计的。使用 Python 3.12+ 的 sqlite3.connect(..., autocommit=True)autocommit=False 选项创建的连接不受支持,因为在这些连接上 commit()rollback() 的行为不同。

我承认我之前没想过 sqlite-utils 会对 Python 3.12 新增的较新 autocommit 设置做出什么反应。结果发现"在这些连接上行为不同"意味着几乎整个测试套件都会失败,所以我与模型合作确保这种差异不会破坏库的工作方式。

以及 GPT-5.5 的最终审查

我曾经认为让一个模型审查另一个模型的工作的想法有些荒谬——感觉奇怪地迷信。问题在于它确实有效——我已经习惯性地让 Anthropic 最好的模型审查 OpenAI 的工作,反之亦然,因为这种情况经常产生有趣的结果,值得一做。我向 Codex Desktop 和 GPT-5.5 xhigh 提供了以下 prompt:

审查自上次 RC 以来的变更。同时确认 changelog 是最新的。

这足以发现两个值得调查的问题:

发现 [P1] sqlite_utils/db.py:663 db.query() 现在仅在调用 db.execute() 之后才拒绝非行语句,而 sqlite_utils/db.py:705 首先自动提交这些写入。因此 db.query("update ...") 会引发 ValueError,但 update 已经提交。对于一个文档说明为"只能用于返回行的 SQL"的方法来说,这是一个令人惊讶的副作用。 [P1] sqlite_utils/db.py:672 通过 db.query()INSERT ... RETURNING 仅在返回的 generator 被完全耗尽后才提交。db.query("insert ... returning ...") 不进行迭代,或常见的 next(db.query(...)) 用法,会使事务保持打开状态,写入可能在关闭时回滚。这与 docs/changelog.rst:15docs/python-api.rst:232 相矛盾,后者声称无需迭代即可生效。

我将这些粘贴到一个新的 Fable 会话中,它运行了一些实验来确认问题:

两个发现都得到了确认。db.query() 首先调用 self.execute(),它会自动提交写入,然后才检查 cursor.description——因此 db.query("update ...") 在引发 ValueError 之前就提交了 update。而 INSERT ... RETURNING 的提交位于返回的 generator 末尾,因此除非你耗尽迭代器,否则它永远不会触发——next(db.query(...)) 或未迭代的调用会使事务保持打开状态,这与 changelog 和文档的承诺相矛盾。

这是修复的 PR,以及完整的 Claude Code 对话记录。审查这段代码帮助我更好地建立了 SQLite 事务语义边界情况的心智模型!

成本

估计(无补贴)成本为 $149.25,我将 Claude Max 升级到了 $200/月计划(之前是 $100/月),以在截至 7 月 7 日 Fablepocalypse 的剩余时间内增加我的 Fable 配额——届时即使 Claude Max 订阅者也将不得不为模型支付完整的 API 成本。我很好奇如果我直接支付这些费用会花多少钱。起初我以为这些数字我无法获取,因为我使用 Claude Code for web 远程运行了工作,然后我意识到我可以在现有会话中运行 AgentsView 来获取成本估算!

运行 "uvx agentsview --help"​ 然后使用该工具计算此会话的成本

Claude 弄清楚了如何使用 session list --include-children 命令,并得出了以下结果:

对话 模型 成本
主会话 claude-fable-5 $141.02
API 表面扫描 agent claude-fable-5 $2.40
事务/原子性审查 agent claude-fable-5 $2.39
后 rc1 提交审查 agent claude-fable-5 $1.72
迁移审查 agent claude-fable-5 $1.40
Prompt 计数 agent claude-opus-4-8 $0.32
总计 $149.25

我很高兴我订阅了这个计划!我真的应该遵循自己的建议,更多地依赖使用更便宜模型的子 agent。这是 claude.ai/settings/usage 目前显示给我的:

我目前还有几个其他主要的 Fable 驱动项目正在进行中,目标是在价格上涨前将 Fable 条用满到 100%。

sqlite-utils 4.0rc2 的完整发布说明

以下是该 RC 的完整发布说明。我让 Fable 在每次变更落地时将其添加到 changelog 的"Unreleased"部分,并随进展进行审查。这有一个很好的副作用:changelog 的提交历史充当了发布中每个变更的简洁摘要。过去我坚持手动编写发布说明,但老实说,这些比我自己的写得更好。发布说明是一个很好的例子,说明我乐意将其外包给 agent,因为它们需要枯燥、可预测且准确。

Breaking changes:

其他所有内容:

标签: projects, sqlite, sqlite-utils, annotated-release-notes, anthropic, claude, coding-agents, claude-code, agentic-engineering, gpt, claude-mythos-fable

译自 Simon Willison · 博客 · 录于 二〇二六年七月五日