раздел 06 · подстраница 2
Структура settings.json
Hooks живут в settings.json - один JSON-файл, три уровня (глобально / проект / локально). Знание иерархии решает 90% проблем "почему мой hook не срабатывает".
Зачем
Без чёткого понимания, где какой файл и какой приоритетнее, легко настроить hook, который никогда не вызовется (или вызовется не там). А ещё .local иногда коммитится по ошибке - и приватные настройки уезжают в git.
Три файла, три уровня
| Файл | Зона действия | Коммитим в git? |
| ------------------------------------- | ----------------------------------- | --------------- |
| ~/.claude/settings.json | Глобально, для всех проектов | Нет (домашний) |
| <project>/.claude/settings.json | Конкретный проект, для всей команды | Да |
| <project>/.claude/settings.local.json | Только ваша машина в этом проекте | Нет (gitignore) |
Иерархия применения
Hooks из всех трёх файлов складываются, не перекрывают друг друга. Сработают и глобальный, и проектный, и локальный - в порядке: project → user → local (зависит от события и от настроек, обычно все три по очереди).
Permissions и общие настройки - наоборот, локальный перекрывает проектный, проектный перекрывает глобальный.
Структура JSON
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "scripts/format.sh"
}
]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"done\" with title \"Claude\"'"
}
]
}
]
}
}
Ключевые поля
hooks- корневой объект, ключи = имена событий (см. предыдущую страницу).matcher- паттерн для фильтрации. ДляPreToolUse/PostToolUse- имена инструментов через|. ДляUserPromptSubmit- regex по тексту промпта."*"или пусто - срабатывает всегда.hooks[]- массив команд. Каждая - объект{ "type": "command", "command": "..." }.command- shell-команда. Может быть путь к скрипту или однострочник. Запускается вbash -c.
matcher: примеры
"matcher": "Edit|Write|MultiEdit" // только при правке файлов
"matcher": "Bash" // только bash-вызовы
"matcher": "*" // любой инструмент
"matcher": "" // тоже любой
Для UserPromptSubmit - regex по тексту:
"matcher": "deploy|выкат|пуш на прод"
Полный пример settings.json
{
"model": "claude-opus-4-7",
"theme": "dark",
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "[[ \"$CLAUDE_FILE_PATHS\" == *\".env\"* ]] && echo 'blocked .env' >&2 && exit 1; exit 0"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/format.sh"
},
{
"type": "command",
"command": ".claude/hooks/run-tests.sh"
}
]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude finished\" with title \"Claude Code\"'"
}
]
}
]
},
"permissions": {
"allow": ["Bash(npm test:*)", "Bash(git status)"]
}
}
Безопасные правки: команда /update-config
JSON ломается легко: лишняя запятая, незакрытая скобка - и Claude вообще перестаёт стартовать. Чтобы не править руками, используйте встроенный skill:
> /update-config
> /update-config добавь hook PostToolUse: автоформат .py через black
> /update-config разреши Bash(npm test:*) глобально
Skill сам найдёт нужный уровень файла, провалидирует JSON и применит правку.
Где живут вспомогательные скрипты
Удобный паттерн - класть hook-скрипты в .claude/hooks/<name>.sh рядом с settings:
project/
├── .claude/
│ ├── settings.json
│ ├── settings.local.json ← в .gitignore
│ └── hooks/
│ ├── format.sh
│ └── run-tests.sh
Не забудьте chmod +x .claude/hooks/*.sh.
Антипаттерны
- Хардкод абсолютных путей в проектном settings.json - сломаете коллегам.
- Команды в
commandс двойными кавычками без экранирования - JSON порвётся. - Положить
settings.local.jsonпод git - утекут локальные настройки и токены. - Hooks без таймаута - заблокируют Claude навсегда при зависании.
Полезные ссылки
- Settings reference - формат файла
- Hooks reference - описание hooks-секции
- Permissions - allow/deny форматы