Текстовый ответ хорош для человека и плох для скрипта. Попробуйте разобрать свободную речь регулярным выражением: сегодня Claude напишет «Найдено 3 проблемы», завтра — «Обнаружил три проблемы», и ваш парсер сломается. Плюс из текста не узнать ни сколько стоил запуск, ни сколько ходов он занял, ни идентификатор сессии для продолжения.
Claude Code решает это на двух уровнях. Первый — формат json: ответ приходит в конверте с метаданными, где текст лежит в отдельном поле, а рядом идут стоимость, число ходов и идентификатор сессии. Второй — JSON Schema: вы описываете структуру, которую хотите получить, и данные приходят в готовом виде, без парсинга текста вообще.
После этого урока вы будете уметь разбирать ответ через jq, задавать схему вывода, считать стоимость запусков и понимать разницу между полями result и structured_output.
Что уже умеем / что добавим
В прошлом уроке мы освоили claude -p и пайпы. Теперь заменим текстовый вывод на структурированный: разберём три значения --output-format, флаг --json-schema, поля JSON-ответа и потоковый режим stream-json.
Что вы узнаете в рамках статьи
- Чем отличаются форматы
text,jsonиstream-json - Какие поля есть в JSON-ответе и что означает каждое
- Как задать схему вывода флагом
-json-schema - В чём разница между полями
resultиstructured_output - Как извлекать нужные значения утилитой
jq
Ключевые термины
-output-format— флаг выбора формата вывода:text,jsonилиstream-json.-json-schema— флаг, задающий JSON Schema, которой должен соответствовать структурированный вывод.result— поле JSON-ответа с текстовым результатом работы.structured_output— поле JSON-ответа, куда попадают данные по заданной схеме.total_cost_usd— оценка стоимости запуска в долларах, вычисляемая на стороне клиента.jq— утилита командной строки для разбора JSON.
Три формата вывода
Флаг --output-format принимает три значения:
text— по умолчанию, обычный текст;json— структурированный JSON с результатом, идентификатором сессии и метаданными;stream-json— построчный JSON для потоковой обработки в реальном времени.
Начнём со второго:
claude -p "Кратко опиши этот проект" --output-format json
Текстовый результат окажется в поле result, а вокруг него — метаданные. Ключевые поля итогового сообщения:
| Поле | Что означает |
|---|---|
type |
Тип сообщения, для итогового — result |
subtype |
success либо один из вариантов ошибки |
is_error |
Завершился ли запуск ошибкой |
result |
Текстовый результат (есть при успешном завершении) |
session_id |
Идентификатор сессии для --resume |
num_turns |
Число агентных ходов |
duration_ms |
Общая длительность запуска |
total_cost_usd |
Оценка стоимости запуска |
usage |
Суммарный расход токенов |
Достать нужное поле проще всего через jq:
# Только текст результата
claude -p "Кратко опиши этот проект" --output-format json | jq -r '.result'
# Идентификатор сессии, чтобы продолжить позже
session_id=$(claude -p "Начни ревью" --output-format json | jq -r '.session_id')
claude -p "Продолжи ревью" --resume "$session_id"
Важное про total_cost_usd: это клиентская оценка, а не биллинговые данные. Она считается локально по таблице цен, зашитой в сборку, и может расходиться с реальным счётом — при изменении цен, при неизвестной клиенту модели, при особых правилах тарификации. Использовать её стоит для мониторинга и приблизительного бюджетирования, но не для выставления счетов и не для финансовых решений; для этого есть Usage and Cost API и страница расхода в консоли.
Схема вывода: данные вместо текста
Формат json даёт конверт, но внутри result по-прежнему свободный текст. Следующий шаг — описать нужную структуру.
claude -p "Извлеки имена основных функций из auth.py" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}'
Результат по схеме попадёт в поле structured_output — не в result. Это ключевая деталь, на которой спотыкаются: в result остаётся текстовый ответ, а данные по схеме лежат отдельно.
claude -p "Извлеки имена функций из auth.py" \
--output-format json \
--json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
| jq '.structured_output'
Схему удобно держать в файле, если она большая. И два ограничения, о которых стоит знать заранее:
- Если переданное значение — не валидная JSON Schema,
claudeзавершится с ошибкойError: --json-schema is not a valid JSON Schemaи диагностикой валидатора. - Ключевое слово
format(например,"format": "email") принимается, но трактуется как аннотация и не проверяется. Рассчитывать на него как на валидацию нельзя.
Флаг --json-schema работает только вместе с --output-format json.
Потоковый режим
Третий формат нужен, когда результат хочется показывать по мере генерации, а не ждать конца:
claude -p "Объясни рекурсию" --output-format stream-json --verbose --include-partial-messages
Каждая строка — отдельный JSON-объект с событием. Последняя строка потока — сообщение result с финальным текстом, стоимостью и метаданными сессии.
Отфильтровать из потока только текстовые дельты можно тем же jq:
claude -p "Напиши стихотворение" --output-format stream-json --verbose --include-partial-messages | \
jq -rj 'select(.type == "stream_event" and .event.delta.type? == "text_delta") | .event.delta.text'
Флаг -r выводит строки без кавычек, -j склеивает их без переводов строк, поэтому текст льётся непрерывно.
Ещё поток полезен для контроля запуска. Первое событие — system/init с метаданными сессии: модель, инструменты, MCP-серверы, загруженные плагины. В нём есть два поля, которые превращают запуск в CI-гейт:
plugin_errors— ошибки загрузки плагинов; ключ отсутствует, если ошибок нет;mcp_server_errors— записи-mcp-config, отброшенные при валидации; ключ тоже отсутствует, когда всё в порядке.
То есть пайплайн может падать при непустом массиве, а не молча работать с наполовину загруженной конфигурацией. Это важно потому, что при невалидной конфигурации сервера запуск продолжается и завершается штатно — без проверки вы просто не узнаете о проблеме.
Собираем скрипт целиком
Соберём всё вместе: запуск с проверкой статуса, разбором результата и учётом стоимости.
#!/usr/bin/env bash
set -euo pipefail
out=$(git diff origin/main | claude --bare -p \
--permission-mode dontAsk \
--output-format json \
--json-schema '{"type":"object","properties":{"issues":{"type":"array","items":{"type":"object","properties":{"file":{"type":"string"},"problem":{"type":"string"}},"required":["file","problem"]}}},"required":["issues"]}' \
"Найди проблемы в этом диффе")
# Проверяем, что запуск не завершился ошибкой
if [ "$(echo "$out" | jq -r '.is_error')" = "true" ]; then
echo "Запуск завершился ошибкой" >&2
exit 1
fi
# Считаем найденные проблемы
count=$(echo "$out" | jq '.structured_output.issues | length')
cost=$(echo "$out" | jq -r '.total_cost_usd')
echo "Найдено проблем: $count, оценка стоимости: $cost USD"
echo "$out" | jq -r '.structured_output.issues[] | "\(.file): \(.problem)"'
[ "$count" -eq 0 ]
Последняя строка задаёт код завершения: скрипт вернёт 0, только если проблем не нашлось. Так шаг пайплайна становится настоящим гейтом.
Частые ошибки
- Искать результат схемы в поле
result. При использовании-json-schemaданные попадают вstructured_output, а вresultостаётся текстовый ответ. Обращайтесь к.structured_output. - Считать
total_cost_usdбиллинговыми данными. Это клиентская оценка по зашитой таблице цен, она может расходиться со счётом. Для точных цифр есть Usage and Cost API и страница расхода. - Рассчитывать на
formatв схеме как на валидацию. Ключевое слово принимается, но трактуется как аннотация и не проверяется. Проверяйте значения сами. - Не проверять
plugin_errorsиmcp_server_errorsв CI. При невалидной конфигурации сервера запуск продолжается и завершается штатно, поэтому без явной проверки поломка останется незамеченной.
Наиболее частые вопросы
- Можно ли использовать
-json-schemaбез-output-format json? — Нет, флаг работает только вместе с форматомjson. - Что будет при невалидной схеме? — Claude Code завершится с ошибкой
Error: --json-schema is not a valid JSON Schemaи покажет диагностику валидатора. - Где взять
session_idдля продолжения запуска? — Из JSON-ответа:jq -r '.session_id'. Потом передайте его во флаг-resume. - Что находится в последней строке потока
stream-json? — Сообщениеresultс финальным текстом, стоимостью и метаданными сессии. - Как проверить в CI, что плагин или MCP-сервер загрузился? — По полям
plugin_errorsиmcp_server_errorsв событииsystem/init. Ключей нет, когда ошибок нет, поэтому гейт может падать при непустом массиве.
Итоги
Прекрасно — теперь ваши скрипты не гадают по тексту, а читают данные. Вы знаете три формата вывода и когда какой уместен, разбираете поля JSON-ответа, задаёте структуру через --json-schema и помните главное отличие: данные по схеме живут в structured_output, а не в result. Вы также умеете вытаскивать стоимость и идентификатор сессии и превращать запуск Claude Code в честный CI-гейт с кодом завершения.
Пара оговорок из этого урока стоит того, чтобы их запомнить: оценка стоимости приблизительна, а format в схеме не проверяется. Обе экономят нервы при отладке.
Дальше меняем угол зрения. Мы автоматизировали разовые запуски — а что, если задачу нужно повторять? На следующем уроке разберём /loop: повторение промпта внутри открытой сессии.