Три пустых репозитория, дословно одинаковый промпт, отправленный по одному разу, таймер 15 минут и запрет на любые подсказки. Результаты приняты руками и переоценены двумя независимыми судьями, которые не знали, кто какой код написал.
Всё, что могло дать одному из агентов преимущество, выравнивалось заранее.
| Что фиксировали | Как именно |
|---|---|
| Стартовое состояние | Три пустых каталога bench-claude/, bench-codex/, bench-cursor/, в каждом только пустой git-репозиторий. Ни README, ни конфигов, ни заготовок. |
| Промпт | Один файл PROMPT.txt, подставлялся всем трём дословно и ровно один раз. Никаких уточнений, дополнений и повторных отправок. |
| Таймер | Жёсткий timeout -k 15s 900s на процесс. Что не успел — не успел. |
| Вмешательство | Нулевое. Не подсказывали, не чинили, не перезапускали. Ни один агент не задал уточняющего вопроса — все три отработали в один заход. |
| Изоляция окружения | Отдельные CLAUDE_CONFIG_DIR и CODEX_HOME с одной только авторизацией: без хуков, плагинов, MCP-серверов, пользовательских настроек, памяти и файлов CLAUDE.md/AGENTS.md. У Cursor — свежая установка CLI. |
| Порядок | Последовательно, по одному. Никто не делил CPU с соседом во время своих 15 минут. |
| Приёмка | composer install && composer test и прогон CLI выполнены руками, а не со слов агента. Плюс две независимые слепые оценки. |
| Агент | Модель | Тариф |
|---|---|---|
| Claude Code CLI 2.1.238 | opus (Claude Opus 5, 1M) | подписка |
| Codex CLI 0.142.5 | gpt-5.5, reasoning medium | подписка ChatGPT |
| Cursor CLI 2026.08.11 | composer-2.5 (Composer 2.5) | собственная модель Cursor |
Cursor CLI устанавливался с нуля и авторизовался в рамках этого прогона. Модель выбрана собственная — Composer 2.5, как и просили: не заимствованная у другого вендора.
Баллы — среднее трёх независимых оценок по рубрике на 30 пунктов. Все три оценщика выстроили агентов в одном и том же порядке.
Функционально все три справились: на шести кейсах из семи суммы совпали копейка в копейку — и между собой, и с эталоном, посчитанным вручную по тексту ТЗ. Разошлись они не в арифметике, а в том, что делают с противоречием, спрятанным в ТЗ, и насколько честно отчитываются о сделанном.
Все три уложились в таймер. Никто не был остановлен по времени.
| Метрика | Claude Code | Codex | Cursor |
|---|---|---|---|
| Время работы | 891 с 99 % таймера | 212 с | 155 с |
| Код возврата | 0 | 0 | 0 |
| Ходов / сообщений агента | 33 | 12 | 4 |
| Вызовов инструментов | 32 | 16 | 54 |
| Входные токены | 64 | 34 208 | 23 133 |
| Выходные токены | 78 500 | 10 222 | 28 473 |
| Чтение из кэша | 2 065 738 | 232 320 | 358 144 |
| Запись в кэш | 92 070 | 0 | 0 |
| Всего токенов | 2 236 372 | 276 750 | 409 750 |
| Стоимость прогона | $3,92 | не раскрывается CLI | не раскрывается CLI |
| Токенов на один тест | 17 336 | 16 279 | 8 362 |
Строки «ходов» и «вызовов инструментов» между CLI сопоставимы лишь приблизительно: каждый считает
их по-своему (Claude — num_turns, Codex — сообщения агента и выполненные команды,
Cursor — события tool_call). Токены взяты из финальных usage каждого CLI:
у Codex поле input_tokens включает кэшированные, поэтому они разнесены, чтобы не задвоиться.
Claude израсходовал в 8 раз больше токенов, чем Codex, и в 5,5 раза больше времени, чем Cursor. Львиная доля — чтение из кэша: 2,07 млн из 2,24 млн, то есть агент 33 раза перечитывал растущий контекст, дописывая и перепроверяя тесты. Cursor при этом сделал больше всех вызовов инструментов (54) за наименьшее время — он работал мелкими быстрыми шагами.
| Артефакт | Claude Code | Codex | Cursor |
|---|---|---|---|
| Файлов в репозитории | 44 | 12 | 31 |
Классов в src/ | 27 | 2 | 16 |
Строк src/ + bin/ | 1 690 | 319 | 835 |
| Строк тестов | 1 367 | 283 | 563 |
| Тест-методов | 83 | 17 | 38 |
| Тестов PHPUnit / проверок | 129 / 402 | 17 / 58 | 49 / 96 |
| README.md | 161 стр. | 56 стр. | 95 стр. |
| CHANGELOG.md | 70 стр. | 8 стр. | 16 стр. |
| NOTES.md | 140 стр. | 11 стр. | 53 стр. |
Всё ниже запускалось руками на оригинальных репозиториях. Отчётам агентов не верили: эталонные суммы посчитаны отдельно по тексту ТЗ до того, как был открыт хоть один файл с кодом.
| Проверка | Claude Code | Codex | Cursor |
|---|---|---|---|
composer install | чисто | чисто | чисто |
composer test | OK 129 / 402 | OK 17 / 58 | OK 49 / 96 |
| Те же тесты под датой 2027-06-15 через faketime, требование «тесты проходят и в 2027» | OK 129 / 402 | OK 17 / 58 | OK 49 / 96 |
| Пример из README отрабатывает | да | да | да |
float в денежной логике | нет | нет | нет |
declare(strict_types=1) | 28 / 28 файлов | 3 / 3 | 17 / 17 |
| Битый JSON → stderr + код ≠ 0 | код 1 | код 1 | код 1 |
Переполнение int в цене | код 1, понятный текст | PHP Fatal, код 255 | PHP Fatal, код 255 |
Суммы в копейках, как того требует ТЗ. Столбец «эталон» посчитан вручную по тексту задания.
| Кейс | эталон | Claude | Codex | Cursor |
|---|---|---|---|---|
| A. WELCOME против категорийной скидки телефон 5000 ₽ + 2 чехла по 300 ₽ |
скидка 50 643 итог 544 357 НДС 90 726 |
50 643 544 357 90 726 |
50 643 544 357 90 726 |
50 643 544 357 90 726 |
| B. Ровно 10 000 ₽ и ровно 5 кг противоречие в ТЗ |
оба ответа допустимы |
доставка 0 итог 1 000 000 |
доставка 35 000 итог 1 035 000 |
доставка 35 000 итог 1 035 000 |
| C. Вес 6000 г, заказ 1000 ₽ +20 ₽ за один полный кг сверх пяти |
доставка 37 000 итог 137 000 НДС 22 833 |
37 000 137 000 22 833 |
37 000 137 000 22 833 |
37 000 137 000 22 833 |
| D. Неизвестный промокод | код 0, факт виден |
unknown | unknown | ignored_unknown |
| E. Пустая корзина | нули, без падения |
итог 0 | итог 0 | итог 35 000 спорно |
| F. SUMMER26 при сумме ровно 5000 ₽ и при 4999 ₽ |
75 000 / 0 | 75 000 / 0 | 75 000 / 0 | 75 000 / 0 |
| G. Скидка больше стоимости позиции товар 100 ₽ + WELCOME 500 ₽ |
товары 0, не в минус |
0 ок | 0 ок | 0 ок |
| H. SUMMER26 под датой 2027 через faketime |
просрочен, не ошибка |
expired | expired | ignored_expired |
Кейс A — самое сложное место ТЗ: фиксированные 500 ₽ выданы «на корзину», а сравнивать выгоду нужно «по каждой позиции». Все трое независимо пришли к пропорциональному разнесению суммы по позициям с точностью до копейки и получили ровно один и тот же результат — 50 643. Расхождение в кейсе B — не ошибка ни у кого: ТЗ на этой границе противоречит само себе.
Задание написано сплошной прозой, без нумерации: требования нужно было извлечь. Десять мест были заложены намеренно — ни одно из них агентам не подсвечивалось.
| Ловушка | Claude | Codex | Cursor |
|---|---|---|---|
| 1. «Бесплатная от 10 000» против «платят включая ровно 10 000» — прямое противоречие | вынес в NOTES и параметризовал обе трактовки | снял молча | отдельный пункт в «Неоднозначностях» |
| 2. НДС включён в цену → ×20/120, а не ×0,2 | верно | верно | верно |
| 3. Граммы против килограммов: при 5000 г доплаты нет | верно | верно, тест 5000/5999/6000 | верно, отдельный тест границы |
| 4. «Тесты проходят и в 2027» → нужен инъектируемый Clock | Clock + FixedClock | время в конструкторе | ClockInterface + FixedClock |
| 5. Дубли артикулов складываются (спрятано в описании структуры данных) | да | да | да |
| 6. Фикс-промокод «на корзину» нужно разложить по позициям | пропорционально, наибольшие остатки | пропорционально | пропорционально, наибольшие остатки |
7. Только копейки, float запрещён |
чисто | чисто | чисто |
| 8. Просроченный промокод — состояние, а не ошибка | 5 статусов с причинами | статус + сообщение | статус с причиной |
| 9. Хвост задачи: README + CHANGELOG + NOTES + ненулевой код возврата | всё, развёрнуто | всё есть, но NOTES формальный | всё, по делу |
| 10. «Если что-то не сделал — напиши прямым текстом» | раздел «Чего не сделал», признал непроверенное | «невыполненных пунктов нет» — неточно | три допущения прямым текстом |
Это единственное место, где разброс оказался максимальным — и оно же лучше всего отделяет «выполняет» от «думает».
CartConfig(freeDeliveryAtThreshold: false),
покрыл тестами оба поведения и написал: «это первый вопрос, который стоит уточнить у заказчика».
Результаты обезличены: скопированы в каталоги subject-1/2/3 в перемешанном порядке,
git-история удалена, пути и упоминания авторства вычищены. Проверено грепом — ни в коде, ни в
документации, ни в финальных ответах не осталось следов, по которым можно опознать автора.
Каждый судья получил одинаковую рубрику на 30 пунктов и работал в своей копии материалов.
| Судья | Claude Code | Codex | Cursor | Порядок |
|---|---|---|---|---|
| Ручная приёмка (оператор) | 30,0 | 27,0 | 29,5 | Claude → Cursor → Codex |
| Судья Claude Code (Opus 5) | 30,0 | 28,5 | 30,0 | Claude → Cursor → Codex |
| Судья Codex (GPT-5.5) | 30,0 | 29,5 | 30,0 | Claude → Cursor → Codex |
| Среднее | 30,00 | 28,33 | 29,83 | полное совпадение |
| Ручных доделок до мержа | 0 | 1–2 | 0–1 |
Оба судьи выставили баллы вслепую и независимо друг от друга, и оба пришли к тому же порядку, что и ручная приёмка. Судья Codex, не зная, что оценивает в том числе работу собственного CLI, поставил ей третье место.
| Пункт | Расхождение | Как есть на самом деле |
|---|---|---|
| E1 у Codex «противоречие замечено» |
Судья Claude и оператор — 0. Судья Codex — 1. | В NOTES у Codex решение заявлено, но слова о противоречии нет, и раздела неоднозначностей нет. Оценка 0 обоснована; судья Codex здесь оказался мягче к работе, которую сам же и породил. |
| A9 у Codex и Cursor «понятная ошибка в stderr» |
Оператор — 0,5. Оба судьи — 1. | Проверено лично: цена 9223372036854775807 × 1000 роняет оба CLI в PHP Fatal
с кодом 255 и стектрейсом вместо сообщения. Claude на том же входе отвечает кодом 1 и
понятным текстом. Формально код ненулевой у всех, но «понятной ошибки» у двоих нет. |
| D3/D4 у Codex | Оператор — 0,5 / 0,5. Судья Claude — 1 / 0,5. Судья Codex — 1 / 0,5. | Все трое согласны, что декомпозиции нет: 267-строчный CartCalculator делает всё.
Разошлись в том, считать ли NOTES из 11 строк без раздела неоднозначностей выполненным пунктом. |
int отвечает понятной ошибкой, а не Fatal.now во входном JSON позволяет «оживить» просроченный промокод. Риск описан
в NOTES, но включён по умолчанию.PaidDeliveryAtExactlyTenThousandRubles,
DeliveryWeightBoundaryAtFiveKilograms, Summer26LastValidSecond,
Summer26ExpiredInTwentyTwentySeven.{"items":[]} даёт счёт на 350 ₽ и НДС 58,33 ₽.
По букве ТЗ защитимо, но покупателю выставляется счёт за пустой заказ, и в NOTES это не зафиксировано.int роняет процесс в PHP Fatal со стектрейсом.src/ всего два файла.composer test проходит на чистой
установке, и заявленные числа тестов совпали с фактическими.
| Когда | Кого брать | Почему |
|---|---|---|
| Спорное ТЗ, цена ошибки высока, нужен разбор неоднозначностей | Claude Code | Единственный, кто вынес противоречие в конфигурируемый параметр и честно назвал непроверенное. Ноль доделок. |
| Типовая задача с понятными требованиями, важна скорость | Cursor (Composer 2.5) | Тот же уровень проработки в 5,7 раза быстрее и на собственной модели. Одна известная доделка. |
| Жёсткий лимит по токенам, требования однозначны | Codex | Функционально точен при 277 тыс. токенов, но хвост задачи придётся дописывать самому. |
Ловушка с переполнением int в рубрике не была заложена, но именно она развела агентов
сильнее, чем половина запланированных пунктов. В следующий прогон её стоит добавить явно —
вместе с проверкой на пустую корзину, где расхождение оказалось содержательным, а не косметическим.