The Daily Newsstand · Free, Always
Thursday, September 17, 2026

Спросил у Claude Code одно слово — он отправил на сервер 305 килобайт. Вскрыл трафик двух кодинг-агентов

Translate

Вопрос был «Ответь одним словом: работает». Двадцать девять символов. Claude Code отправил на сервер запрос на 305 042 байта, сервер посчитал его как 113 460 токенов и положил в кэш. Ответ пришёл из четырёх токенов. По списочному прайсу API такой «привет» стоит как чашка кофе; на подписке этого не видно, но контекстное окно он съедает точно так же.

Документацию про системный промпт и инструменты я читал. Хотелось увидеть само тело запроса: поставить между агентом и сервером прокси, сохранить всё, что уходит, и разобрать по частям. Сделал это для Claude Code и для Codex CLI. Дальше цифры, скрипт на сорок строк, чтобы повторить у себя, и список того, что после этого стоит отключить.

Как ловил

Прокси — сорок строк на Python: слушает локальный порт, принимает POST, сохраняет тело и заголовки в файл, пересылает дальше по HTTPS как есть и отдаёт ответ обратно. Оба агента умеют ходить на свой адрес:

  • Claude Code: переменная окружения ANTHROPIC_BASE_URL=http://127.0.0.1:8766.

  • Codex CLI: в config.toml у провайдера base_url = "http://127.0.0.1:8765/v1".

Схема http и локальный адрес ни одного из них не смутили. Токен авторизации прокси в файл не пишет, в сохранённых заголовках он заменён на <redacted>; всё остальное лежит как есть.

#!/usr/bin/env python3
import http.server, http.client, json, os, sys, time, ssl
UPSTREAM = os.environ.get("UPSTREAM", "api.example.com")
LOGDIR = os.environ.get("LOGDIR", "captures")
os.makedirs(LOGDIR, exist_ok=True)
seq = [0]

class H(http.server.BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    def log_message(self, *a): pass
    def _fwd(self):
        seq[0] += 1
        n = seq[0]
        body = self.rfile.read(int(self.headers.get("Content-Length") or 0))
        base = f"{LOGDIR}/{time.strftime('%H%M%S')}-{n:03d}"
        open(base + ".req.json", "wb").write(body)
        json.dump({k: ("<redacted>" if k.lower() == "authorization" else v)
                   for k, v in self.headers.items()}, open(base + ".req.headers.json", "w"), indent=1)
        hdrs = {k: v for k, v in self.headers.items()
                if k.lower() not in ("host", "content-length", "transfer-encoding", "accept-encoding")}
        hdrs["Host"] = UPSTREAM
        hdrs["Content-Length"] = str(len(body))
        t0 = time.time()
        conn = http.client.HTTPSConnection(UPSTREAM, timeout=600, context=ssl.create_default_context())
        conn.request(self.command, self.path, body=body, headers=hdrs)
        resp = conn.getresponse()
        data = resp.read()
        open(base + ".resp.txt", "wb").write(data)
        self.send_response(resp.status)
        for k, v in resp.getheaders():
            if k.lower() not in ("content-length", "transfer-encoding", "content-encoding", "connection"):
                self.send_header(k, v)
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)
        print(f"[{n}] {self.command} {self.path} -> {resp.status} {len(body)}B req, {time.time()-t0:.1f}s", flush=True)
    do_POST = _fwd
    do_GET = _fwd

http.server.ThreadingHTTPServer(("127.0.0.1", int(sys.argv[1])), H).serve_forever()

Запуск: UPSTREAM=api.example.com LOGDIR=captures python3 proxy.py 8766, где api.example.com — хост, на который агент ходил до этого. Стриминг прокси не поддерживает: он дожидается всего ответа и отдаёт его целиком. Обоим агентам этого хватило.

Дальше в пустой папке задавал каждому один и тот же вопрос — «Ответь одним словом: работает» — и смотрел, что легло в captures/.

Условия. Claude Code 2.1.274 на ноутбуке, запущен через -p из окружения десктоп-приложения, с моими обычными настройками: один MCP-сервер (Figma), один плагин с хуком на старт сессии, модель с окном в миллион токенов. Codex CLI 0.154.0 на сервере, в изолированном CODEX_HOME без плагинов, модель попроще, reasoning low. Пара не идеальная, и соревнования тут нет. Мне была интересна анатомия запроса: из чего он состоит и какие части можно выкинуть. «Голые» конфигурации я тоже сравнил, это ниже.

Claude Code: 305 килобайт

Тело первого запроса по частям. Символы посчитаны по JSON, доли — от размера тела, поэтому они приблизительные.

Часть

Символов

Доля

tools — 137 инструментов

259 423

~85%

из них 107 от MCP-сервера Figma

138 477

~45%

из них 30 встроенных

120 672

~40%

messages — служебное сообщение: окружение, список скиллов, инструкции MCP, вывод хука плагина

28 691

~12%

system — системный промпт, четыре блока

12 830

~4%

messages — два system-reminder: почта аккаунта, правила подписи коммитов

1 132

меньше 1%

messages — мой вопрос

29

0,01%

Три наблюдения.

Первое. Системный промпт — далеко не главный расход. Четыре блока: строка с версией клиента для биллинга (81 символ), «You are a Claude agent…» (62), правила про честный отчёт о результате (907) и основной промпт (11 780). Итого 12,8 тысячи символов, четыре процента тела.

Второе. Главный расход — описания инструментов, и большая их часть в этой сессии мне не нужна. 107 инструментов Figma на 138 тысяч символов уехали в запрос, где я прошу ответить одним словом. Самое длинное описание среди встроенных — у инструмента Artifact, 29 185 символов, больше всего системного промпта. Для сравнения: Bash — 2 666, Read — 1 629, Write — 663.

Третье. Плагины и хуки попадают в запрос целиком. Хук на старте сессии добавил 29 тысяч символов отдельным сообщением с ролью system. Одна и та же простыня в каждом запросе каждой сессии.

По мелочи, что ещё уходит: metadata.user_id с идентификаторами устройства, аккаунта и сессии; max_tokens: 64000; thinking: adaptive; output_config.effort: high; в заголовке anthropic-beta перечислены четырнадцать бета-флагов. Маркеры cache_control с TTL в час стоят в трёх местах: после короткого блока идентичности, после основного системного промпта и после служебного сообщения. Значит, в кэш ложится всё, включая инструменты; на втором ходу это читается оттуда.

Ответил агент, кстати, по-китайски: «工作». В настройках у меня стоит язык ответов, и одно слово он выбрал на нём. Настройки, как видите, тоже едут в запрос.

Второй запрос, о котором я не просил

В логе прокси на каждый ход два POST. Второй — 18,5 килобайта: без инструментов, max_tokens: 3072, системный промпт на 16,7 тысячи символов. Открыл. Это классификатор: ему дают хвост ответа агента и просят выбрать одно из четырёх состояний (done, working, blocked и ещё одно), чтобы решить, слать ли мне push-уведомление на телефон. У меня включены уведомления о завершении задачи, вот они так и работают. Стоит это 6 234 токена из кэша плюс 68 входных на каждый ход. Немного. Но я об этом не знал, и в логах usage самого агента этот запрос не виден.

Codex CLI: 29 килобайт

Тот же вопрос. Тело 29 750 байт, сервер посчитал 9 817 токенов.

Часть

Символов

Доля

instructions — системный промпт

12 950

~44%

tools — 10 инструментов

9 277

~31%

input[0] — developer-сообщение со списком скиллов

5 490

~18%

input[1]<environment_context>: cwd, shell, дата, таймзона

443

~1,5%

input[2] — мой вопрос

29

0,1%

client_metadata — id установки, сессии, треда, хода

~1 000

~4%

Забавная деталь: системные промпты двух агентов почти одной длины, 12 830 и 12 950 символов. Вся десятикратная разница в размере запроса — в инструментах и в том, что вокруг них.

Десять инструментов Codex: exec_command, write_stdin, apply_patch, request_user_input, view_image, три штуки про goals, tool_search и web_search. У него тоже есть отложенные инструменты: tool_search ищет по описаниям и подключает нужное по запросу, вместо того чтобы тащить всё сразу. У Claude Code такой механизм есть в интерактивном режиме (часть инструментов помечена как deferred), но в моём запуске через -p все 137 уехали целиком.

На ноутбуке с плагинами Codex в прошлой статье показал 14 809 токенов на тот же вопрос: там длиннее список скиллов. Порядок тот же.

Снимаю слои

Больше всего меня интересовало, сколько из 113 тысяч токенов убирается флагами, без правки конфигов.

Конфигурация

Байт

Токенов в кэш

Инструментов

Claude Code как есть: MCP + плагин

305 042

113 460

137

--strict-mcp-config --mcp-config '{"mcpServers":{}}' (без MCP)

173 513

61 274

29

то же + --setting-sources "" (без настроек и плагинов)

138 879

48 967

28

Codex CLI без плагинов

29 750

9 817

10

Один MCP-сервер стоил 52 тысячи токенов на запрос. Плагин с хуком — ещё около 12 тысяч (часть этой разницы — более короткий системный промпт: без настроек агент сменил модель на дефолтную, и промпт у неё 9 231 символ). «Голый» Claude Code — 49 тысяч токенов, «голый» Codex — 10 тысяч. Разница в пять раз, и почти вся она в 28 встроенных инструментах на 117 тысяч символов.

Вариант без настроек, понятно, не для жизни: отвалились мои модель и язык. Но как нижняя граница он полезен.

Что это значит для окна и кэша

Окно. 113 тысяч токенов на старте — это 11% от миллиона. Будь окно в 200 тысяч, инструменты и служебный текст заняли бы 57% ещё до первого вопроса. Когда агент «тупеет к концу сессии», полезно помнить, с какого места он её начал.

Кэш. В первом запросе всё ушло в cache_creation_input_tokens, чтения ноль. Через пять минут я запустил новую сессию с той же конфигурацией без MCP, но уже с вызовом инструмента. Первый запрос этой сессии: cache_read 61 292, cache_creation 255. Другая сессия, другой session_id, а префикс прочитан из кэша целиком: кэш здесь адресуется содержимым. Поэтому на API-тарифе весь этот балласт пишется один раз в час, дальше читается по дешёвой ставке. Ровно до тех пор, пока префикс совпадает байт в байт. Подключили новый MCP-сервер посреди дня, обновили плагин — префикс другой, сто тысяч токенов пишутся заново.

Скорость. Запрос на 305 килобайт по логу прокси шёл 3,3 секунды, без MCP — 3,2. На ощущения объём не влияет. Влияет на окно и на счёт.

Что с этим делать

  1. MCP-серверы подключать в конфиге проекта, а не глобально. Figma мне нужна в двух папках; остальным она стоила бы 52 тысячи токенов на каждый запрос.

  2. Проверить хуки и плагины. Если хук вываливает в сессию десятки тысяч символов, лучше оставить в нём короткий указатель на файл: агент прочитает его, когда понадобится.

  3. Раз в месяц запускать прокси и смотреть первый запрос. Пятнадцать минут, и видно, что накопилось. Скрипт для подсчёта:

import json, sys
b = json.load(open(sys.argv[1]))
sz = lambda o: len(json.dumps(o, ensure_ascii=False))
for k in ("system", "instructions", "messages", "input", "tools"):
    if k in b:
        print(f"{k:13} {sz(b[k]):>8}")
for t in sorted(b.get("tools", []), key=sz, reverse=True)[:10]:
    print(f"  {t.get('name') or t.get('type'):40} {sz(t):>8}")
  1. Если пользуетесь агентом через -p или SDK, а не интерактивно, помнить, что отложенных инструментов там может не быть и в запрос уходит всё.

  2. Считать вместе со вторыми запросами. Классификатор для уведомлений в usage агента не попадает, зато попадает в счёт.

Чего я не проверял

  • Интерактивный режим Claude Code. Там инструменты могут уходить как deferred, и первый запрос, вероятно, меньше. Мерил только -p.

  • Codex с плагинами и MCP на ноутбуке через прокси. 14 809 из прошлой статьи получены без прокси, по usage.

  • Claude Code в чистом терминале без десктоп-окружения. Часть из 30 встроенных инструментов (Artifact, Workflow, DesignSync) явно от десктоп-приложения; в обычном терминале их должно быть меньше. Запуск с очищенным окружением у меня потребовал логина заново, и я не стал.

  • Другие версии обоих клиентов. Набор инструментов меняется от релиза к релизу, у вас числа будут свои.

Если поставите прокси и получите свои цифры — напишите в комментариях, сколько инструментов и сколько токенов уходит у вас в первом запросе. Особенно интересно, у кого больше 137.

Короткие заметки и замеры между статьями пишу в канале: https://t.me/agent_field_notes

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

View the original on Хабр

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.