$149 и 37 промптов: как Саймон Уиллисон отдал финальный релиз sqlite-utils на откуп Claude Fable
Саймон Уиллисон выпустил sqlite-utils 4.0rc2 с помощью Claude Fable — 37 промптов, 34 коммита, критический баг с потерей данных. Разбираем процесс по шагам.

Саймон Уиллисон — соавтор Django и создатель Datasette — выпустил sqlite-utils 4.0rc2, и большую часть работы над ним сделала языковая модель. Не как эксперимент ради хайпа, а как рабочий процесс с реальным багом, который мог уйти в стабильный релиз и сломать данные пользователей.
Вот как это было устроено и что из этого можно взять в свой процесс.
Зачем вообще подключать модель к финальному ревью
Уиллисон придерживается SemVer строго: мажорные версии выходят редко, и каждая должна быть максимально чистой. Перед тем как выпустить стабильный 4.0, он запустил Claude Fable (через Claude Code for Web прямо с iPhone) с одним конкретным промптом:
`` Final review before shipping a stable 4.0 release - very important to spot any last minute things that would be a breaking change if we fix them later ``
Модель вернула отчёт с пятью «release blockers» — проблемами, которые сам автор ещё не встретил.
Самый опасный баг: delete_where() молча терял данные
Первый и худший из найденных багов — в методе Table.delete_where() в файле sqlite_utils/db.py:2948. Метод выполнял DELETE через голый self.db.execute() без обёртки atomic(). В результате соединение оставалось в состоянии in_transaction=True, и все последующие вызовы atomic() уходили в ветку savepoint — и никогда не коммитились.
Воспроизводится так:
```python import sqlite_utils
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()
Переоткрываем базу — там строки [0, 1, 2]
Удаление, строка 50 и таблица u — всё пропало
```
Это именно потеря данных, а не просто неожиданное поведение. При этом, как отмечает сам Уиллисон, баг можно было бы исправить в 4.0.1 — это не архитектурный просчёт, который потребовал бы 5.0. Но всё равно очень хорошо, что он не ушёл в релиз.
Как выглядел рабочий процесс: 37 промптов за один день
Весь процесс занял 37 промптов, 34 коммита и затронул 30 файлов: +1 321 строка добавлена, −190 удалено. Уиллисон работал с моделью итерационно: давал задачу, ждал 10–15 минут пока Fable «перемалывает» её, и возвращался с следующим шагом.
Параллельно он ходил на парад по случаю 4 июля в Half Moon Bay — и это не метафора, а буквальное описание того, как работают агентные задачи: чем сложнее задача, тем больше времени можно делать что-то другое.
Ключевые изменения касались модели транзакций — главной новой фичи RC. Итоговое поведение библиотеки теперь описывается так:
```python from sqlite_utils import Database
db = Database("data.db") db.table("news").insert({"headline": "Dog wins award"})
Строка уже сохранена — commit() не нужен
```
Каждый метод, пишущий в базу (insert(), upsert(), update(), delete(), delete_where(), transform() и остальные), теперь сам оборачивается в транзакцию и коммитит её до возврата. Явный commit() нужен только в двух случаях: когда вы группируете операции через db.atomic() или когда сами открыли транзакцию через db.begin().
Почему одна модель ревьюит другую — и это работает
После того как Fable закончил работу, Уиллисон прогнал финальное ревью через GPT-5.5 (Codex Desktop, режим xhigh) с простым промптом:
`` Review changes since the last RC. Also confirm that the changelog is up-to-date. ``
GPT-5.5 нашёл два новых P1-бага:
db.query("update ...")сначала выполняет и коммитит UPDATE, а потом бросаетValueError— потому что метод задокументирован только для SELECT-запросов. Данные уже изменены, а пользователь получает исключение.
INSERT ... RETURNINGчерезdb.query()коммитится только после того, как генератор полностью исчерпан. Если вы делаетеnext(db.query("insert ... returning ..."))и не итерируете дальше — транзакция остаётся открытой и может откатиться при закрытии соединения. Это противоречит документации.
Уиллисон признаётся, что раньше считал идею «одна модель ревьюит другую» суеверием. Теперь это часть его стандартного процесса: лучшая модель Anthropic ревьюит работу OpenAI и наоборот.
Подводные камни
Python 3.12 и autocommit. В Python 3.12 появился параметр sqlite3.connect(..., autocommit=True/False). Оказалось, что sqlite-utils с ним практически не работал — почти весь тест-сьют падал. Fable нашёл это в документации, которую сам же написал, и Уиллисон отдельно проработал совместимость.
Документация как первый артефакт для ревью. Уиллисон специально читает документацию, которую пишет модель, раньше кода — это быстрый способ понять, что вообще изменилось, и поймать концептуальные ошибки до погружения в diff.
Агент не заменяет финальный взгляд автора. Переключение на ноутбук и ревью через GitHub PR interface — это осознанный шаг. Интерфейс меняет восприятие: в браузере видишь diff иначе, чем в IDE или терминале.
Стоимость не нулевая. $149.25 за один рабочий день — это не «бесплатно». Для open-source проекта это реальные деньги, и важно понимать, что речь идёт о Max-подписке с ограниченным временем доступа к Fable.
Что попробовать дальше
Если хотите воспроизвести похожий процесс на своём проекте:
- Начните с промпта на «release blockers» перед любым мажорным релизом — даже если уверены в коде
- Используйте разные модели для генерации и ревью: Anthropic → OpenAI или наоборот
- Читайте документацию, которую пишет модель, до кода — это экономит время
- Следите за поведением транзакций в sqlite-utils 4.0: новая модель автокоммита меняет привычные паттерны работы с библиотекой