How Claude Fable shipped sqlite-utils 4.0rc2 for 150 dollars
Simon Willison used Claude Fable to audit, refactor, and document a critical transaction-handling release of sqlite-utils. The agent identified five release-blocking issues, including a severe data-loss bug, across 37 prompts.
Impact: Medium
Why it matters
This showcases how advanced AI agents can perform deep database-level debugging and documentation tasks autonomously under human guidance, significantly reducing development costs.
TL;DR
- 01Claude Fable successfully audited sqlite-utils 4.0rc2, categorizing 5 issues as release blockers, including a major data-loss bug.
- 02Cross-model verification using GPT-5.5 xhigh and Codex Desktop caught two additional edge cases that Fable then resolved.
- 03Using agentsview inside Claude Code, the developer calculated an unsubsidized session cost of approximately $149.25.
Key facts
- Total code changes
- +1,321 -190 lines
- Files modified
- 30 separate files
- Fable cost estimate
- $149.25 (using agentsview)
- Prompts run
- 37 prompts
- Commits made
- 34 commits
Finding the Poisoned Transaction Bug
During a pre-release audit of sqlite-utils 4.0, Claude Fable flagged a severe data loss issue. The Table.delete_where() method executed queries directly through db.execute() without wrapping them in an atomic transaction:
db["t"].delete_where("id = ?", [0]) # Leaves conn.in_transaction = TrueBecause in_transaction remained True, all subsequent calls to atomic() took the savepoint branch and never committed their work to disk. When the database connection was closed, all modifications were lost.
The Cross-Model Review Workflow
To ensure maximum safety, Simon Willison executed a cross-model review of Fable's changes. He prompted Codex Desktop and GPT-5.5 xhigh with: Review changes since the last RC. Also confirm that the changelog is up-to-date.
This secondary review discovered two major bugs: 1. Surprising Side-Effect in Query Verification: db.query("update...") raised a ValueError for write statements, but only *after* executing and auto-committing the update. 2. Dangling Transaction Generator: An INSERT ... RETURNING query via db.query() committed only when the returned generator was fully exhausted. Calling next() on it left the transaction open, risking a rollback on connection close.
Fable was subsequently used to verify and patch both edge cases in a new session.
Try it in 2 minutes
db["t"].delete_where("id = ?", [0]) # conn.in_transaction is now Truepython
✓ When to use
- When you want to group several write operations into atomic transactions with db.atomic().
✕ When NOT to use
- If you are using Python's default autocommit options which behave differently in sqlite-utils.
What to do today
- Review your codebase's transaction handling, especially with SQLite db.execute() wrappers.
- Consider using cross-model reviews (e.g. GPT-5.5 on Claude's work) to find subtle edge cases.
- Use agentsview to monitor API costs when working with AI coding agents.
Sources