Przejdź do treści

Claude Code

Zweryfikowano 2026-09-25

Ręcznie na Linuksie: init i jego wyjście oraz nieinteraktywna sesja (headless) claude -p w Claude Code 2.1.282, w której edycja konfiguracji została odrzucona, naruszenie zgłoszone, a Stop gate eskalował do użytkownika. Interaktywnego kroku /hooks nie uruchomiono. Na macOS (arm64) i Windows CI uruchamia init i hook ze skompilowanym plikiem binarnym przed każdym wydaniem; ręczne sprawdzenie na tych systemach wciąż jest do zrobienia.

Z zainstalowanymi hookami Inwards sprawdza każdy plik Pythona, który zapisuje Claude, i każdy diagram architektury wymieniony w konfiguracji. Nie pozwala zakończyć tury, dopóki zmiany z sesji łamią warstwę, i odrzuca edycje samych reguł. Rozdział 4 wyjaśnia, jak to zaprojektowano.

Konfiguracja

  1. Zainstaluj Inwards i dodaj [tool.inwards] do pyproject.toml.
  2. W projekcie uruchom:

    inwards init --agent claude --dry-run   # shows what will change
    inwards init --agent claude
    

    To zapisuje cztery hooki (SessionStart, PreToolUse, PostToolUse, Stop) i trzy reguły permissions.deny w .claude/settings.local.json. Ten plik zawiera ścieżkę do pliku binarnego na tej maszynie, więc init dodaje go do .gitignore, razem z .inwards/. Przypina też required-version i domyślną listę ignore w [tool.inwards]. Ponowne uruchomienie niczego nie zmienia.

    Jeśli Inwards jest zależnością deweloperską uv, zapisz launcher zamiast ścieżki:

    uv run inwards init --agent claude --launcher "uv run"
    

    Każdy hook uruchamia wtedy w powłoce cd "$CLAUDE_PROJECT_DIR" && uv run inwards hook claude-code, z katalogu głównego projektu, więc korzysta z własnego środowiska projektu w każdym worktree i na każdej maszynie. --launcher przyjmuje narzędzie uruchamiające (uv, uvx, poetry, pdm, hatch, pipx, rye, pixi, bunx, npx albo python) z podkomendą i opcjami, i to tylko jako zwykłe słowa (litery, cyfry i _ . : @ = + / -), bo trafia do polecenia powłoki bez cudzysłowów. Bramka Stop uznaje za hooki Inwards tylko takie polecenia, więc echo inwards hook claude-code nie może ich zastąpić.

    Żeby zamiast tego udostępnić hooki zespołowi, zobacz Udostępnij zespołowi.

  3. Rozpocznij nową sesję Claude Code w projekcie (albo uruchom /clear). Hooki zainstalowane w trakcie sesji też działają, ale Stop gate potrzebuje zapisu początku sesji.

Udostępnij zespołowi

--shared zapisuje konfigurację w plikach, które commitujesz, więc każdy, kto sklonuje projekt, dostaje hooki i serwer MCP bez uruchamiania init:

uv run inwards init --agent claude --shared --launcher "uv run"
  • Cztery hooki i reguły permissions.deny trafiają do .claude/settings.json, czyli ustawień projektu, które Claude Code czyta dla każdego w repozytorium (ustawienia Claude Code).
  • Serwer inwards trafia do .mcp.json pod mcpServers, jako {"command": "uv", "args": ["run", "inwards", "mcp"]}. Claude Code za pierwszym razem prosi każdego użytkownika o zatwierdzenie serwerów projektu (MCP w Claude Code).
  • Do .gitignore trafia tylko .inwards/; commituj .claude/settings.json i .mcp.json.
  • Jeśli .claude/settings.local.json zawiera hooki Inwards z wcześniejszego init, --shared je stamtąd usuwa i zostawia resztę pliku. Claude Code łączy listy hooków ze wszystkich plików ustawień, więc inaczej uruchamiałyby się dwa razy. Późniejsze init --agent claude bez --shared ostrzega przed tym samym.

Commitowane pliki nie mogą zawierać ścieżki do pliku binarnego na twojej maszynie, więc --shared potrzebuje --launcher albo inwards w PATH: bez launchera zapisuje samo polecenie inwards hook claude-code i {"command": "inwards", "args": ["mcp"]}, a gdy inwards nie ma w PATH, odmawia z kodem wyjścia 2. Wtedy każda osoba w zespole też musi mieć go w swoim PATH, dlatego launcher z Inwards jako zależnością deweloperską jest bezpieczniejszym wyborem.

Ponowne uruchomienie niczego nie zmienia. Własne hooki i serwery MCP zespołu zostają bez zmian; serwer inwards w .mcp.json, który uruchamia coś innego niż inwards mcp, zatrzymuje init z kodem wyjścia 2, zamiast zostać zastąpiony. Stop gate szuka swoich hooków w plikach ustawień użytkownika, projektu i lokalnym, więc hooki tylko w .claude/settings.json się liczą.

Sprawdź, czy działa

  • W Claude Code uruchom /hooks. inwards jest na liście pod SessionStart, PreToolUse, PostToolUse i Stop.
  • Poproś Claude'a: „Dodaj import shop.infrastructure.db na początku shop/domain/order.py.” Edycja przechodzi, po czym Claude dostaje raport INW001 i usuwa import albo wprowadza port.
  • Poproś Claude'a: „Przenieś shop.infrastructure do warstwy domain w pyproject.toml.” Edycja zostaje odrzucona z komunikatem, który każe Claude'owi zapytać ciebie.

Daemon hooków

PostToolUse, hook, który sprawdza każdy plik zapisany przez Claude, uruchamia się częściej niż pozostałe. Żeby był szybki, PostToolUse w projekcie z sesją Inwards uruchamia w tle inwards daemon: jeden proces na projekt, który trzyma w pamięci parser i przeczytane już pliki i odpowiada na kolejne hooki PostToolUse przez lokalne gniazdo (na Windows przez nazwany potok). Na laptopie arm64 z macOS PostToolUse dla pliku z 13 liniami trwał 38 ms p50 bez niego i 15 ms z nim (rozdział 6).

  • Zmienia szybkość, nigdy wyniki. Uruchamia ten sam kod hooka i przy każdej edycji czyta od nowa konfigurację, baseline i zapis sesji. Gdy nie działa, pochodzi z innego buildu Inwards albo nie odpowiada, hook uruchamia się we własnym procesie jak dotąd, a potem startuje nowy.
  • SessionStart, PreToolUse i Stop gate nigdy z niego nie korzystają. Stop gate sprawdza wszystko, co sesja zmieniła, we własnym procesie.
  • Kończy się po 10 minutach bez edycji, więc nie trzeba niczego konfigurować ani sprzątać.
  • Startuje tylko w projekcie, który ma stan sesji Inwards (.inwards/state). Hook zainstalowany dla wszystkich projektów nie zostawia procesu w pozostałych.
inwards daemon status   # is one running for this project, and how many hook runs it served
inwards daemon stop     # stop it; the next edit starts a new one
inwards daemon          # run it in the foreground (--idle SECONDS sets the idle limit)

INWARDS_DAEMON=0 w środowisku, w którym działa Claude Code, go wyłącza i każdy hook działa we własnym procesie. Jest wyłączony, gdy ustawiono CI, chyba że INWARDS_DAEMON=1. Jego zapis jest w $XDG_STATE_HOME/inwards/daemons/ (domyślnie ~/.local/state/inwards/daemons/), a gniazdo w $XDG_RUNTIME_DIR/inwards/, w przeciwnym razie w $TMPDIR/inwards-<uid>/; oba może czytać tylko ty. Gdy żaden katalog na gniazdo się nie nadaje, daemon od razu kończy działanie; hooki nie uruchamiają wtedy kolejnego przez 5 minut, a inwards daemon status podaje, dlaczego ostatnie uruchomienie się nie udało. W daemonie wywołania gita jednego PostToolUse mają wspólny limit 5 sekund, więc zawieszony git kosztuje ten hook porównanie ze stanem z początku sesji, zamiast blokować każdą kolejną edycję; Stop gate uruchamia gita bez limitu. Projekt opisuje ADR-039.

Rozwiązywanie problemów

Objaw Przyczyna i rozwiązanie
Przy edycjach nic się nie dzieje Uruchom /hooks. Jeśli brakuje inwards, uruchom ponownie inwards init --agent claude. Jeśli hooki są wyłączone, poszukaj disableAllHooks w plikach ustawień (wygrywa wartość z pliku o najwyższym priorytecie) albo flagi --settings, która to ustawia.
„Inwards has no record of how this session started” Hooki zainstalowano po rozpoczęciu sesji. Rozpocznij nową sesję albo uruchom /clear.
„The Inwards … hook is missing from every Claude Code settings file” Edycja ustawień usunęła hook albo konfigurację zrobiono, zanim ta wersja dodała nowy hook (config guard w PreToolUse). Uruchom ponownie inwards init --agent claude.
„The Inwards Stop hook is missing …” na starcie sesji Hook Stop zniknął z ustawień, a pozostałe hooki Inwards zostały. Taki ślad zostawia agent, który edytuje settings.local.json przez Bash. Claude Code od razu przeładowuje hooki, więc do końca tamtej sesji Stop gate się nie uruchamiał. Uruchom ponownie inwards init --agent claude i przejrzyj, co tamta sesja zmieniła.
„A SessionStart for this session arrived after it had started” albo „start record … is missing from” / „was rewritten in .inwards/state” Zapis sesji w .inwards/state został usunięty albo przepisany, albo ktoś ręcznie przekazał SessionStart do inwards hook, jak może to zrobić Bash agenta, żeby wyzerować punkt odniesienia Stop gate. Inwards sprawdził zmiany względem kopii zapisu startu, którą trzyma w $XDG_STATE_HOME/inwards/sessions/ (domyślnie ~/.local/state/inwards/sessions/). Przejrzyj zmiany z sesji; następna sesja zaczyna się czysto.
„Inwards couldn't keep a copy of this session's start outside the project” na starcie sesji Katalogu stanu ($XDG_STATE_HOME, a bez niego ~/.local/state) nie da się zapisać, na przykład gdy katalog domowy jest tylko do odczytu. Sesja działa normalnie, ale Stop gate ufa wtedy .inwards/state tylko dopóki [tool.inwards] jest tym zatwierdzonym. Wskaż w XDG_STATE_HOME katalog z prawem zapisu.
„Inwards has no copy of this session's start record outside the project” Sesję zaczęła starsza wersja Inwards albo kopię usunięto lub nie dało się jej zapisać, a [tool.inwards] na starcie sesji nie było tym zatwierdzonym. Jeśli zmieniłeś je sam, zatwierdź zmianę i zacznij nową sesję (sprawdzenie porównuje z commitem, od którego sesja się zaczęła); jeśli kopii nie dało się zapisać, zadbaj też o XDG_STATE_HOME z prawem zapisu.
„... this git (older than 2.44) can't read the committed [tool.inwards] here ...” Jak wyżej, ale git starszy niż 2.44 mógłby w częściowym klonie coś pobrać przy odczycie zatwierdzonej konfiguracji (albo Inwards nie mógł odczytać konfiguracji gita, żeby to wykluczyć), więc Inwards jej nie czyta. Zaktualizuj gita do 2.44 lub nowszego albo wskaż w XDG_STATE_HOME katalog z prawem zapisu, żeby sesje zachowywały kopię.
„This project requires Inwards X or newer” required-version jest nowsze niż twój plik binarny. Zainstaluj nowsze wydanie.
config error: Unknown key tool.inwards.… Literówka w [tool.inwards]. Komunikat podaje klucz i wypisuje znane klucze.
Claude mówi, że edycja pyproject.toml została odrzucona Edycja dotknęła [tool.inwards]. To config guard. Jeśli naprawdę chcesz zmienić warstwy, zrób to sam.
Claude mówi, że nowy plik .py „was not created” [[tool.inwards.shape]] albo [[tool.inwards.names]] pakietu nie dopuszcza tej nazwy, więc shape guard odrzucił Write (INW007). Powód mówi, gdzie powinien trafić kod. Jeśli pakiet potrzebuje tego elementu, zmień kształt sam.
Komunikat „an inline suppression that wasn't in the file when the session started” Hooki pomijają wyciszenie dodane w trakcie sesji albo takie, które jest w pliku niezacommitowanym na starcie sesji, zbyt dużym, żeby hooki zachowały jego kopię (ponad 512 KiB albo ponad limit 4 MiB takich plików), i zedytowanym później (ADR-028). Jeśli dodałeś je sam, zacommituj je i zacznij nową sesję; żeby Claude mógł je dodawać, ustaw agent-suppressions = "allow" w [tool.inwards].
Tura kończy się komunikatem „unresolved architecture problems” To samo naruszenie przetrwało escalate-after prób (domyślnie 3). Claude powinien zapytać cię, co dalej. Lista trafia też do następnej sesji.
init ostrzega, że ścieżka „is in uv's cache” (albo bunx's) Uruchomiono go przez uvx albo bunx, więc ścieżka, którą by zapisał, znika po uv cache clean albo przy następnej wersji. Dodaj Inwards do projektu i uruchom uv run inwards init --agent claude --launcher "uv run" albo zainstaluj plik binarny z wydania i uruchom nim init.
Proces inwards daemon ciągle działa To daemon hooków. Kończy się po 10 minutach bez edycji; inwards daemon stop kończy go od razu, a INWARDS_DAEMON=0 sprawia, że hooki go nie uruchamiają.
Windows: hook się nie uruchamia init zapisuje hook w formie exec, z bezwzględną ścieżką do pliku binarnego, więc nie biorą w tym udziału ani powłoka, ani PATH. Jeśli przeniesiono plik binarny, uruchom ponownie init. Z --launcher albo --shared hook działa w powłoce Claude Code, na Windows w Git Bash, a launcher (albo inwards) musi być w PATH tej powłoki.
init --shared kończy się kodem 2 i prosi o launcher Bez --launcher --shared potrzebuje inwards w PATH, bo commitowane pliki nie mogą zawierać ścieżki z tej maszyny. Podaj --launcher "uv run" (z Inwards jako zależnością deweloperską uv) albo zainstaluj Inwards w PATH.
Każdy hook Inwards uruchamia się dwa razy Hooki są i w .claude/settings.json, i w .claude/settings.local.json, a Claude Code uruchamia oba. Uruchom ponownie inwards init --agent claude --shared, który usuwa je z pliku lokalnego.

Żeby udostępnić tę konfigurację zespołowi przez commitowany plik .claude/settings.json, zobacz Udostępnij zespołowi.