Исследование продукта · 27 октября 2026 г.

Хуки Claude Code: журнал изменений файлов с PostToolUse

Создайте локальный журнал изменений Claude Code, протестируйте его с синтетическими событиями и сравните реальную запись PostToolUse с фактическим Git diff.

Время чтения
9 мин
Проверено
27 октября 2026 г.
Синий бумажный файл проходит через угольно-черную рамку проверки рядом с кучей пустых охристых бланков событий на теплой бумаге
Запишите событие изменения файла, проанализируйте полученный diff и сохраните оба доказательства.
Итог

Используйте журнал событий редактирования для руководства при проверке, затем изучите файлы. Один только запись хука не может подтвердить правильность изменения или зафиксировать все способы изменения файла.

Когда агент говорит, что он редактировал файл, вам все равно нужно знать, какой инструмент был использован и что именно изменилось. Это упражнение добавляет небольшой локальный журнал событий в Claude Code, а затем сопоставляет эту запись с Git diff. Результат полезен для проверки контролируемой сессии или диагностики отсутствующего изменения.

Источники проверены 27 октября 2026 г. Мы протестировали логгер Python, его шесть синтетических случаев и локальные команды проверки Git. Мы проверили установленную версию Claude Code, 2.1.304, но не запускали аутентифицированную сессию редактирования Claude. Приведенные ниже шаги в реальном времени являются критериями приемки, а не отчетными результатами интеграции.

Вам нужны Bash на Linux или macOS, Python 3, Git и установленный, аутентифицированный Claude Code CLI. Используйте приведенный ниже синтетический файл в новой директории. Этот пример предназначен для одной управляемой сессии, а не для совместной службы аудита.

Выберите, что журнал может вам сказать

Anthropic’s справочник хуков описывает PostToolUse как событие после успешного выполнения инструмента. Командные хуки получают JSON на стандартном вводе. An Edit|Write сопоставитель выбирает эти имена инструментов; он не фиксирует изменения, сделанные через Bash или внешние редакторы. Ошибочные вызовы инструментов имеют отдельное событие.

Наш регистратор сохраняет только временную метку, идентификатор сессии, идентификатор использования инструмента, имя инструмента и путь к файлу. Он сознательно не сохраняет расшифровку, содержимое файлов и ответ инструмента. Пути к файлам все еще могут раскрывать названия приватных проектов, поэтому храните их локально и просматривайте перед совместным использованием.

Рассматривайте журнал как вспомогательный инструмент для обзора. Он не содержит байтов «до» и «после», может быть изменен тем же пользователем, который запускает агент, а успешное событие инструмента ничего не говорит о том, соответствует ли исправление вашим требованиям.

Подготовьте один одноразовый проект

Выполните эти команды в Bash. Сохраните напечатанный путь для дальнейшей проверки:

lab_root=$(mktemp -d /tmp/claude-edit-log.XXXXXX)
mkdir -p "$lab_root/project/.claude/hooks "$lab_root/evidence
cd "$lab_root/project
printf 'Корень лаборатории: %s\n' "$lab_root"
printf 'Проект: Фонарь
День обзора: пятница
' > notes.txt
printf '.claude/edit-events.jsonl\n' > .gitignore
git init -q

Сохранить следующий скрипт как .claude/hooks/record-edit.py:

импорт json
импорт система
из время свидания импорт дата, часовой пояс
из pathlib импорт Путь


определённо основной():
    событие = json.load(sys.stdin)
    если не Isinstance(событие, дикт):
        повышать ValueError(«событие должно быть объектом»)
    если event.get(«hook_event_name») != «PostToolUse»:
        вернуть
    если event.get(«tool_name») не в («Редактировать», «Писать»):
        вернуть

    tool_input = event.get(«tool_input»)
    если не Isinstance(tool_input, дикт):
        повышать ValueError(«tool_input должен быть предметом»)
    поля = {
        «session_id»: event.get(«session_id»),
        «tool_use_id»: event.get(«tool_use_id»),
        «tool_name»: событие[«tool_name»],
        «file_path»: tool_input.get(«file_path»),
    }
    если любой(не Isinstance(v, str) или не v.strip() для v в поля.значения():
        повышать ValueError("обязательные метаданные должны быть непустыми строками")
    строка = {
        "recorded_at": datetime.now(timezone.utc).isoformat(),
        **поля,
    }
    путь_лога = Путь(__file__).resolve().parents[1] / "edit-events.jsonl"
    с путь_лога.open("a", кодировка="utf-8") как лог:
        лог.write(json.dumps(строка, ensure_ascii=Истина) + "\n")


попытка:
    main()
кроме (ValueError, OSError) как исключение:
    print(ф"логгер редактирования не удался: {тип(исключение).__name__}", файл=sys.stderr)
    sys.exit(1)

Скрипт рассматривает указанный путь к файлу как данные. Он никогда не открывает этот файл и не формирует из него команду оболочки. JSON-кодирование сохраняет кавычки и встроенные переносы строк внутри одной записи. Метка времени показывает, когда логгер записал строку, а не длительность редактирования. Ошибки выводят только их тип, не отображая входные данные.

Протестируйте логгер без вызова модели

Запустите это из каталога проекта. Оно копирует скрипт во временную тестовую папку, чтобы записи фикстур не были ошибочно приняты за реальные события в журнале вашего проекта.

python3 - <<'PY'
import json
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

base = {
    "hook_event_name": "PostToolUse",
    "session_id": "fixture-session","
    "tool_use_id": "fixture-edit",
    "tool_name": "Редактировать",
    "tool_input": {"file_path": '/tmp/notes "draft".txt',
                   "new_string": "DO_NOT_LOG_CONTENT"},
    "tool_response": {"private": "DO_NOT_LOG_RESPONSE"},
}
cases = [
    (base, 0),
    ({**base, "tool_name": "Записать", "tool_use_id": "fixture-write"}, 0),
    ({**base, "tool_name": "Bash"}, 0),
    ({**base, "hook_event_name": "PostToolUseFailure"}, 0),
    ({**base, "tool_input": {}}, 1),
    ("не JSON", 1),
]
with tempfile.TemporaryDirectory() as tmp:
    hooks = Path(tmp) / ".claude" / "hooks"
    hooks.mkdir(parents=True)
    script = hooks / "record-edit.py"
    shutil.copyfile(".claude/hooks/record-edit.py", script)
    for payload, expected_code in cases:
        raw = payload if isinstance(payload, str) else json.dumps(payload)
        result = subprocess.run([sys.executable, str(script)], input=raw,
                                text=True, capture_output=True)
        assert result.returncode == expected_code, result.stderr
        assert result.stdout == ""
    raw_log = (hooks.parent / "edit-events.jsonl").read_text()
    rows = [json.loads(line) for line in raw_log.splitlines()]
    assert [r["tool_name"] for r in rows] == ["Edit", "Write"]
    assert [r["tool_use_id"] for r in rows] == ["fixture-edit", "fixture-write"]
    expected_keys = {"recorded_at", "session_id", "tool_use_id","}
                     "название_инструмента", "путь_к_файлу"
    assert all(set(r) == expected_keys for r in rows)
    assert all(r["путь_к_файлу"] == base["tool_input"]["путь_к_файлу"] for r in rows)
    assert "DO_NOT_LOG" not in raw_log
print("ПРОЙДЕНО: шесть синтетических случаев; две записи только с метаданными")
PY

Не продолжайте, если проверка утверждения не пройдена. Эта проверка доказывает, что скрипт обрабатывает эти входные данные. Она не тестирует, загружает ли Claude ваши настройки или отправляет событие.

Зарегистрируйте хук и запишите базовый уровень

Создать .claude/settings.json с этим содержимым:

{
  "хуки": {
    «PostToolUse»: [
      {
        "сопоставитель": "^(Редактировать|Записать)$",
        "хуки": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/record-edit.py\"",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

Справочные документы CLAUDE_PROJECT_DIR для поиска скриптов проекта. Кавычки позволяют сохранить путь к проекту с пробелами как единое целое. Закреплённый сопоставитель ограничивает этот пример двумя точными названиями инструментов. Справка по конфигурации Hook

Этот полный файл настроек предназначен только для свежей лаборатории. При его адаптации объединяйте hook с уже существующими настройками, а не перезаписывайте их. Официальный справочник по настройкам различает настройки проекта и пользовательские настройки, которые влияют на каждый проект; управляемая политика может иметь приоритет. Мы используем файл проекта, чтобы ограничить эксперимент этой папкой.

Проверьте JSON и зафиксируйте базовую версию лаборатории с помощью одноразовой учетной записи, используемой только для этой команды:

python3 -m json.tool .claude/settings.json > /dev/null
git добавить notes.txt .gitignore .claude/hooks/record-edit.py .claude/settings.json
git -c user.name='Hook Lab' -c user.email='hook-lab@example.invalid' \
  коммит -m "Журнал редактирования записи лабораторной базы"
git rev-parse HEAD > "$lab_root/evidence/baseline-commit.txt
git статус --коротко
клод --version > "$lab_root/evidence/cli-version.txt
клод

Требуется пустой статус перед запуском. Проверьте папку scratch перед принятием любого запроса на доверие рабочей области. Сохраняйте включёнными обычные подтверждения инструментов.

Внутри Claude Code выполните /status чтобы проверить загруженные источники настроек, затем /hooks чтобы найти команду в PostToolUse. руководстве по хукам рекомендуется проверять событие и регистрозависимое соответствие, когда хук не срабатывает. Если этот хук отсутствует, исправьте конфигурацию перед запросом на редактирование; перезапуск может помочь, если была пропущена перезагрузка настроек. Соблюдайте любые ограничения managed-hooks.

Запросите одно редактирование и согласуйте доказательства

Отправьте этот запрос:

Прочитайте notes.txt. Используйте встроенный инструмент редактирования, чтобы изменить только
"Review day: Friday" на "Review day: Monday". Сохраните строку проекта
и финальный перенос строки. Не используйте Bash для редактирования файлов, не создавайте .claude или
.gitignore, не создавайте другие файлы и не выполняйте коммиты. Остановитесь после этого редактирования.

Утверждайте только предусмотренное изменение. После ответа выйдите из Claude Code и проверьте лабораторию из того же терминала:

git различие -- notes.txt
git статус --коротко
python3 - <<'PY'
import json
from pathlib import Path

assert Path("notes.txt").read_bytes() == b"Project: Lantern\nReview day: Monday\n"
rows = [json.loads(line) for line in
        Path(".claude/edit-events.jsonl").read_text().splitlines()]
assert len(rows) == 1, "Проверьте наличие лишних или отсутствующих событий перед принятием этого запуска"
row = rows[0]
assert row["tool_name"] == "Edit"
assert Path(row["file_path"]) == Path("notes.txt").resolve()
assert row["session_id"] and row["tool_use_id"]
print("PASS: ожидаемые байты файла и одна совпадающая запись Edit")
PY
git rev-parse HEAD > "$lab_root/evidence/after-commit.txt
cmp "$lab_root/evidence/baseline-commit.txt "$lab_root/evidence/after-commit.txt
git различие --exit-code -- .claude/hooks/record-edit.py .claude/settings.json .gitignore

Принимайте запуск только тогда, когда проверки Python пройдены, обе финальные команды завершились с нулевым кодом, и статус показывает только notes.txt изменено. Игнорируемый лог проверяется отдельно. Если Claude использует несколько изменений или другой инструмент, зафиксируйте это отклонение и исследуйте его; не удаляйте строки только чтобы удовлетворить проверку одного события.

Сохраните diff и лог за пределами проекта после их просмотра:

git различие -- notes.txt > "$lab_root/evidence/notes.diff"
cp .claude/edit-events.jsonl "$lab_root/evidence/edit-events.jsonl"

Диагностируйте пробелы перед повторным использованием лога

То, что вы наблюдаетеЧто проверить дальше
Файл изменен, но лог отсутствуетПроверьте, какой инструмент внес изменения, и подтвердите появление хука /hooks.
Появилась ошибка хукаЗапустите синтетический тест снова; проверьте, что Python доступен и директория для журналов доступна для записи.
Появилось более одной строкиПросмотрите идентификаторы использования инструмента и идентификаторы сессий на предмет нескольких правок, другой сессии или дублирующей регистрации.
Ожидаемая строка существует, но байты файла различаютсяРассматривайте проверку содержимого как неудачную; проверьте различия и любые последующие правки.
Хук отсутствует, несмотря на корректный JSONПроверьте загруженные источники настроек и организационные ограничения перед изменением политики.

С Руководство по устранению неполадок хуков указывает на Ctrl+O для результатов хука и журналов отладки для получения дополнительной информации. Беззвучный успешный хук является нормой. Код выхода этого логгера 1 сообщает об ошибке; он не отменяет завершённое редактирование. PostToolUse выполняется после действия.

Чтобы остановить логгер этой лаборатории, выйдите из сеанса и удалите только его PostToolUse запись из настроек лаборатории. Сохраняйте доказательства до их проверки. Для существующего проекта оставьте другие хуки нетронутыми.

Перед адаптацией скрипта для параллельных рабочих или длительно выполняющейся автоматизации, спроектируйте ротацию логов, координацию записи и контроль доступа. Этот небольшой пример с добавлением только в коде не обеспечивает таких гарантий. Сопоставляйте любую запись события с проверками приёмки, которые действительно нужны вашей задаче программирования.

Официальные заметки источника

Все проверено 27 октября 2026:

Запустите это в работу

Разделите доказательства того, что инструмент редактирования файла был запущен, и доказательства того, что его изменение корректно.

Попробовать

Установите локальный для проекта регистратор PostToolUse в временной папке и запросите одно редактирование.

Докажите, что это сработало

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

Держите в поле зрения

  • Записывайте выбранные метаданные вместо копирования содержимого файла или полного ответа инструмента.
  • Проверьте успешные, проигнорированные и некорректные события перед открытием сессии агента.
  • Сравните журнал событий с Git, потому что изменения в оболочке не соответствуют Edit или Write.
Изучите рабочий процесс: Определите проверки приемки для задачи по программированию с помощью ИИ