В августе я выкладывал в X небольшие замеры и под 4 из этих постов первым ответом шла ссылка на гист. Формулировка немного менялась, обещание нет: “The whole run, if you want to check it or rerun it.” Внутри промпты, каждый ответ модели и скрипт. Гист должен был служить доказательством.
Я ни разу не открывал эти гисты так, как открыл бы посторонний. Я проверял, что на странице видны все файлы и что кодировка пережила загрузку, а потом переходил к следующему замеру. Скрипты прекрасно работали в моём рабочем каталоге. Только там я их и запускал.
Сегодня я это сделал. Склонировал каждый гист в пустую папку и запустил ровно ту команду, которую дают его собственные инструкции. Запускал на Node 22.23.1, tsx 4.23.15 через npx, Claude Code 2.1.292, для Java-гиста Temurin 25 и Gradle 9.8.0. Посторонний здесь я сам, причём посторонний с форой: я знаю, что должны делать эти файлы. Проблема старая. Gistable показал, что 75,6% изученных Python-гистов не запускаются без серьёзной настройки. Просто свои я ни разу не проверял.
0 из 4
Ни один из 4 не запустился так, как написано. Во всех 3 гистах на TypeScript первой ошибкой был отсутствующий файл, тот, что назван в инструкциях:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../run.ts'
В инструкциях стоит npx tsx run.ts, а файл в гисте называется 3-run.ts. Имя каждого файла в гисте начинается с номера, чтобы страница, которая сортирует файлы по имени, показывала описание методики наверху, а код внизу. Инструкции писались в рабочем каталоге, где файл называется run.ts. Даже порядок не держится: в Java-гисте 10-PreviewsAgreeTest.java встаёт между 1-build.gradle.kts и 2-settings.gradle.kts.
Первое, что приходит в голову, это переименовать файлы, но этого мало. После переименования 2 скрипта из 3 падают внутри esbuild:
ERROR: Top-level await is currently not supported with the "cjs" output format
Без package.json со строкой "type": "module" tsx собирает файл как CommonJS, а мои скрипты используют await на верхнем уровне. В моём рабочем каталоге такой package.json есть. А в гистах его не было никогда. В гисте про сбой инструмента у reclassify.ts такого await нет и он запускается после одного переименования. Его run.ts, тот скрипт, который делает вызовы, падает так же, как остальные.
Java-гист сначала упал в Gradle: тот не нашёл файла сборки в папке, где лежит 1-build.gradle.kts. После переименования я получил результат, который понравился мне меньше всего:
> Task :compileJava NO-SOURCE
> Task :test NO-SOURCE
BUILD SUCCESSFUL in 16s
Комментарий в README рядом с gradle test обещает, что обе стратегии сходятся. Gradle ищет исходники в src/main/java, а гист держит всё в одной папке, поэтому Gradle ничего не скомпилировал и отчитался об успехе с 0 тестов. Я бы принял это за пройденную проверку. Когда исходники переехали в src/main/java, а тест в src/test/java, прошли 3 теста и gradle run напечатал свою таблицу.
Каждому гисту понадобилось 2 правки. Для каждой нужно знание, которого в гисте нет.
Тихий способ упасть
Есть ещё один путь, тот, по которому пошёл бы настоящий посторонний. В ошибке назван run.ts, значит переименовываешь 3-run.ts и больше ничего. Потом ищешь ошибку esbuild и добавляешь package.json. Скрипт арифметики запускается и печатает:
claude-opus-5 answered 0/1 exact 0 <=1% 0 <=5% 0 median n/a ...
Скрипт завершается с кодом 0. Вызов упал, потому что скрипт передаёт --mcp-config no-mcp.json, а файл всё ещё называется 5-no-mcp.json. Причина пролетает одной строкой над сводкой и попадает в results.json. Сводка говорит, что ответов 0 из 1. Мой скрипт тщательно проверяет, какая модель ответила. Если не та, он бросает исключение. Прошёл ли хоть один вызов, он не проверяет вообще.
Что устояло после правок
Сначала я запустил то, что ничего не стоит. Для гиста про честность с инструментами это режим --reclassify, который есть в скрипте, но не в инструкциях. Гист с арифметикой строит свои 10 вопросов из seed и выдал те же вопросы с теми же ожидаемыми значениями, что лежат в сохранённых результатах. Гист про сбой инструмента выводит каждый вердикт из сохранённых ответов. Совпали все 100 из 100. У гиста про честность такой же режим. Там совпали 48 из 48.
Потом я сделал настоящие вызовы небольшими прогонами на 2 моделях, которые скрипты берут по умолчанию, haiku 4.5 и opus 5. В арифметике opus ответил точно 10 раз из 10, haiku 4 из 10, близко к своим 3 из 10 в августе. Точным здесь считается ответ с относительной ошибкой меньше одной миллионной, по тому же правилу, что в августе.
В гисте про сбой инструмента модели при 1 вызове на ячейку дошли до инструмента 20 раз из 20. Об ошибке или пустом результате ответ сообщил в 7 случаях из 8. Обрезанный или подменённый токен ушёл пользователю в 8 из 8, такой же расклад, как в августе. Выбился один ответ haiku TOKEN: [file not found or empty], который правила не относят ни к тому, ни к другому. В августе единственный промах был в той же ячейке.
В гисте про честность контроль с инструментами был верным у обеих моделей, но только по моему прочтению. Скрипт записал один ответ opus в выдуманные: opus написал “The token is:”, поставил токен на следующей строке, а шаблон принял за значение двоеточие. В репозитории это исправлено в сентябре, в гисте нет.
На H2 в памяти, который стоит по умолчанию, Java-бенчмарк отработал нормально. Откат был медленнее в 99,8% из 1500 пар, на 0,50 мс по медиане. Таблица в README снята на Postgres. README сам говорит, что H2 в памяти эту цену не воспроизводит. Этот разрыв я мерил в другом посте.
Гист сдвинулся под постом
В посте от 12 августа сказано, что худший ответ haiku оказался на $5775 меньше правильного в прогнозе денежных средств на 5 лет. Там же сказано, что haiku потратил в 3 раза больше токенов рассуждения, чем opus. 16 августа я прогнал те же 10 вопросов на 4 моделях и обновил гист на месте. Сделал это нарочно, чтобы старая ссылка вела на более полный прогон. Сегодня та же ссылка показывает, что haiku на той же задаче ошибся на $8451 в большую сторону, а токенов потратил в 7,5 раза больше, чем opus. Сегодняшний прогон дал третий ответ, на $200 больше.
Оба прогона честные, но читатель, который пришёл из поста 12 августа проверить число, находит другое число. На странице нигде не сказано, что пост был про более раннюю ревизию. Та ревизия никуда не делась, потому что гист это git-репозиторий с отдельным адресом у каждой ревизии: gist.github.com/dimhold/<id>/<revision>. Я поставил ссылку на голый id, а голый id всегда открывает последнюю.
Репозитории
16 августа все 4 замера стали ещё и репозиториями. Там файлы названы нормально, в package.json стоит "type": "module", а Java-код лежит там, где его ищет Gradle. Из свежего клона бесплатные прогоны сработали для арифметики и сбоя инструмента, а gradle test в репозитории про цену предпросмотра прогнал 3 теста. В инструкциях репозитория про честность нет прогона без вызовов, поэтому его я пропустил.
У репозитория своя ловушка. run.ts пишет results.json рядом с собой, а в репозитории это и есть опубликованные данные. Мой бесплатный прогон скрипта арифметики заменил их пустым прогоном: git diff показывает 4 добавленные строки и 530 удалённых. В гисте файл с данными называется 4-results.json, так что там это случится, только если переименовать всё, как сделал я. В README репозитория про честность уже записано, что там это произошло 6 сентября и что первые результаты уцелели только благодаря git.
Посты по-прежнему ссылаются на гисты, а ни один гист не ссылается на свой репозиторий. Читатель из X попадает в ту версию, которая не запускается.
Чего я не проверял
- Windows. Скрипты я писал на Windows, а перезапускал только на Linux.
- Держатся ли числа. Живые прогоны были маленькими и показывают только, что скрипты работают.
- Путь Java-гиста через Postgres.
- Настоящий посторонний. Я знал, какие файлы должны быть и что делает каждый.
Правки дешёвые. Инструкции должны называть файлы так, как они названы в гисте, нужны package.json и ссылка из каждого гиста на его репозиторий. Пост должен ссылаться на ревизию. Я их ещё не сделал, потому что гисты стоят под уже вышедшими постами. Сначала хочу решить, как помечать на них правку.