Simon Willison released sqlite-utils 4.0rc2 to fix a critical bug in the previous release that caused data loss. The issue occurred in the delete_where() function, which failed to commit its transaction before closing the connection. This left the database in a state where subsequent operations were silently rolled back, effectively deleting rows that had been added after the flawed delete command.
In this article
The initial problem
Willison asked Claude Fable to review the code for a stable 4.0 release. The AI identified five blockers, with the transaction bug being the most severe. The function Table.delete_where() executed a DELETE statement without wrapping it in an atomic transaction. Unlike other write methods, this left the connection in a transaction state. When the script closed, the database rolled back all changes made after that point, including new inserts and updates.
The author fixed the issue in 34 commits across 30 files. The work took 37 prompts and roughly 1,500 lines of code changes. Willison noted that the coding agent required 10 to 15 minutes per task, allowing him to attend the Half Moon Bay 4th of July parade while checking his phone.
Transaction handling changes
The release introduces a new transaction model where every write method, including insert(), update(), and delete(), runs inside its own transaction and commits immediately. Changes are saved to disk as soon as the function call finishes. Users no longer need to call commit() to persist data or close the database to ensure writes are saved.
There are two exceptions to this automatic behaviour. First, if a developer wants to group multiple operations so they all succeed or all fail, they must use db.atomic(). Second, if a user manually starts a transaction with db.begin(), the library will not commit it until the user explicitly does so.
The documentation clarifies that connections created with Python 3.12’s autocommit=True or autocommit=False options are not supported. These settings alter how commit and rollback behave, which broke the library’s standard flow. Willison worked with the model to ensure the library handled these differences without breaking functionality.
A second layer of review
After Fable finished, Willison used GPT-5.5 to review the changes. This cross-model check identified two new issues regarding db.query(). The first involved non-row statements, such as updates, which were auto-committed before the method raised an error for being invalid. The second issue concerned INSERT ... RETURNING queries. The transaction only committed after the returned generator was fully exhausted. If a developer called the method without iterating over the results, the write remained open and could be rolled back on close.
Willison confirmed these bugs with Fable and issued a fix in a subsequent pull request. The process highlighted edge cases in SQLite transaction semantics that Willison had not previously considered.
The cost of the work
Willison upgraded his subscription to the Claude Max $200/month plan to ensure he had enough Fable tokens before the July 7 deadline, when full API costs apply. He ran AgentsView on the existing session to calculate the total expense. The tool estimated the session cost at $149.25, excluding any subsidies.



