Blogg

Hooks i Claude Code 2026: steg-för-steg-konfiguration och 7 färdiga exempel

Markdown

7 färdiga hooks för .claude/settings.json, redo att kopiera och klistra in: skydd av .env, prettier, force-push till main, em-dash och en utvecklingsserver. 4 hanterartyper och 31 händelser.

Läs hela artikeln

Hooks i Claude Code är skalkommandon, HTTP-anrop eller korta LLM-kontroller som körs automatiskt före eller efter en Claude-åtgärd. De lägger till kontroller vid specifika verktygshändelser, formaterar kod efter redigering och ger färsk kontext vid sessionsstart. En matchande PreToolUse-hook kan neka det anropet; regex-exemplen är bekvämlighetsskydd, inte en säkerhetsgräns över varje fil- eller Git-åtkomstväg. Härnäst går vi igenom 4 hanterartyper, 31 livscykelhändelser, ett test för ”när en regel hör hemma i en hook”, 7 färdiga hooks att kopiera till .claude/settings.json och 3 fallgropar jag själv har råkat ut för.

Ailoot-dokumentation: API, nycklar och konfiguration.

Vad är hooks i Claude Code?

Det viktigaste

En hook är ett skalkommando, ett HTTP-anrop, en kort LLM-prompt eller en subagent som Claude Code kör som svar på en specifik livscykelhändelse. Till skillnad från instruktioner i CLAUDE.md, som modellen kan ignorera eller glömma, körs en hook på samma sätt varje gång: det är kod, inte ett förslag.

Under ett halvår hade jag regeln ”redigera aldrig .env direkt” i CLAUDE.md. Claude bröt mot den en gång i månaden. Inte avsiktligt; den glömde helt enkelt regeln mot slutet av en lång session. En gång hamnade en extra token i .env; en annan gång skrev en variabel med ett dubblettnamn över den som användes.

Jag flyttade regeln till en hook, så att den inte längre kunde glömmas. När ett matchande direkt redigeringsverktyg ger en .env-sökväg nekar konfigurerad hook det anropet. Bash-omdirigering, skript, MCP och andra åtkomstvägar behöver separata kontroller; exemplet gör inte filen oföränderlig.

Hooks är skalkommandon, HTTP-endpoints eller LLM-prompter som du definierar och som körs automatiskt vid specifika punkter i Claude Codes livscykel.

— Anthropic, https://code.claude.com/docs/en/hooks

Med andra ord är en hook ditt kommando som Claude Code kör vid en specifik händelse. Varje filredigering, Bash-start, sessionsstart eller slutförd subagent är en händelse som du kan koppla en hook till.

Därför är det här viktigt för en vibe-kodare:

  • Skydda filer som innehåller hemligheter mot oavsiktliga redigeringar.
  • Kör automatiskt prettier/eslint efter varje kodgenerering.
  • Blockera git push --force till main utan uttryckligt tillstånd.
  • Lägg till aktuell kontext i början av varje session.
  • Logga Claudes åtgärder för granskning.

CLAUDE.md hanterar kontext. Hooks hanterar åtgärder. Det är två olika saker, och att blanda ihop dem är det vanligaste nybörjarmisstaget.

Hur skiljer sig hooks från CLAUDE.md och skills?

Det viktigaste

CLAUDE.md är text som modellen läser i början av en session och försöker följa. En hook är ett skalkommando som Claude Code-processen kör som svar på en händelse, oberoende av modellen. En skill är ett namngivet instruktionspaket som Claude kan anropa med Skill-verktyget, ungefär som en funktion. Alla tre hanterar olika typer av uppgifter.

Anthropics dokumentation förklarar uttryckligen skillnaden mellan CLAUDE.md och hooks:

Innehållet i CLAUDE.md levereras som ett användarmeddelande efter systemprompten, inte som en del av systemprompten. Claude läser det och försöker följa det, men det finns ingen garanti för strikt efterlevnad.

— Anthropic, minnesdokumentation, https://code.claude.com/docs/en/memory

Uttryckt på ett annat sätt: innehållet i CLAUDE.md når Claude som användarens första meddelande, inte som en systeminstruktion. Modellen läser det och försöker följa det. Men det finns inga garantier, särskilt inte mot slutet av en lång session, när kontexten växer och äldre instruktioner trängs undan.

En hook fungerar annorlunda. Den finns i settings.json, är inte beroende av modellen och körs av Claude Code-processen vid en specifik händelse: PreToolUse, PostToolUse, SessionStart och andra. En matchande PreToolUse-hook kan neka anrop före körning (exit 2), medan en PostToolUse-hook körs efter åtgärden och kan ge återkoppling eller efterbearbeta en fil; den förebygger inte redan gjord skrivning.

Jämförelse:

FunktionCLAUDE.mdHookSkill
Var den finnsCLAUDE.md i projektets rot.claude/settings.jsonMappen .claude/skills/
Vem som kör denModellen läser den som en del av ett användarmeddelandeClaude Code-processen vid en händelseModellen via Skill-verktyget
Garanterad körningNej (bästa möjliga ansträngning)Konfigurerad matchande command-hook kör kod; fel och returbeteende beror på händelsenVia ett modell-anrop (med ett tillstånd, men inte på shell-nivå)
Vad den används tillProjektkontext, ton, stil, dirigeringBegränsningar, formatering, kontroller av specifika filerKomplexa procedurer i flera steg med parametrar
När den körsVid varje modellmeddelande i en sessionVid en livscykelhändelseNär modellen anropar den
Kan den blockera en åtgärd?NejMatchande PreToolUse kan neka före körning; PostToolUse återställer inte åtgärdenNej

Hela mallen för CLAUDE.md och 6 regler för en levande fil finns i guiden Så konfigurerar du CLAUDE.md 2026: en färdig mall och 6 regler. Läs om skills som namngivna funktioner i Claude Code Skills: Så bygger du ett eget bibliotek.

En hook är den lägsta nivån: ingen modell, ingen tolkning, bara shell.

Hooks är ett av verktygen för kontextteknik, tillsammans med Plan Mode och /compact.

4 hooktyper: Command, HTTP, Prompt, Agent – vilken ska du välja?

Det viktigaste

Claude Code har 4 hanterartyper. Command är ett shell-skript, den vanligaste typen. HTTP skickar en POST-begäran till din URL och används för team och integrationer. Prompt gör ett kort Haiku-anrop för en semantisk kontroll. Agent skapar en subagent med verktygsåtkomst för grundlig validering. Nybörjare behöver Command och Prompt; de täcker 90% av uppgifterna.

TypVad den görNär du ska välja denKomplexitet
CommandKör ett shell-kommando, tar emot JSON på stdin och svarar med en exitkod och stdout90% av fallen: kontrollera sökvägar, formatera, grep i innehåll, lsof, git-åtgärderlåg
HTTPSkickar en POST-begäran med en JSON-kropp till din URL och tar emot JSON som svarNär en regel hör hemma i ett team/CI: centraliserad validering, en gemensam statuspanel, fjärrgranskningmedel (kräver en server)
PromptSkickar en kort prompt till Haiku (som standard) och får ja/nej som semantisk bedömningNär grep inte räcker: ”bedöm om den här texten låter företagsmässig”låg
AgentSkapar en subagent med åtkomst till Read/Grep/Glob för kontroller i flera stegOmfattande validering: ”hitta alla ändrade filer och kontrollera att motsvarande tester finns”hög

Det vanligaste misstaget är att försöka använda Command till något som kräver Prompt. Till exempel är ”använd inte em-dash i .md-filer” en grep-uppgift, så Command passar perfekt. Men ”skriv inte i en företagsmässig ton” är semantiskt och kräver därför Prompt.

Om du precis har börjat kan du använda Command. De 7 färdiga hooksen nedan är alla Command. HTTP/Prompt/Agent kommer senare, när du är redo.

Var finns settings.json och vilka är de 4 konfigurationsnivåerna?

Det viktigaste

Claude Code söker efter konfiguration på 4 platser: företag (distribuerad via MDM), användare (~/.claude/settings.json), projekt (.claude/settings.json i arkivets rot) och lokal (.claude/settings.local.json, inte i git). Alla 4 filer sammanfogas vid sessionsstart, och den lägre nivån har företräde.

NivåSökvägNär den gällerI git?
Företag/Library/Application Support/ClaudeCode/settings.json (Mac), /etc/claude-code/settings.json (Linux)För hela teamet via MDMnej
Användare~/.claude/settings.jsonAlla projekt för en användarenej
Projekt.claude/settings.json i arkivets rotDet här projektet, för hela teametja
Lokal.claude/settings.local.jsonBara det här projektet, bara den här användarennej (i .gitignore)

Jag lägger de 7 hooksen som beskrivs nedan i projektets .claude/settings.json, så följer de automatiskt med när arkivet klonas och Claude använder samma regler i ett nytt fönster. Personliga experimentella hooks lägger jag i .claude/settings.local.json, så att de inte stör teamet.

Alla 4 filer sammanfogas vid sessionsstart. Om reglerna står i konflikt har den lägre nivån företräde (lokal framför projekt, projekt framför användare).

Det snabbaste sättet att skapa din första hook är den interaktiva /hooks-menyn direkt i Claude Code. Välj en händelse i listan, välj en matchare (till exempel Bash eller Edit) och klistra in ett kommando. Claude Code skriver giltig JSON till settings.json åt dig.

31 livscykelhändelser: vilka behöver nybörjare?

Det viktigaste

Claude Code har för närvarande 31 livscykelhändelser, fördelade på 3 frekvenser: en gång per session (SessionStart/SessionEnd), en gång per tur (UserPromptSubmit/Stop) och vid varje verktygsanrop (PreToolUse/PostToolUse). Nybörjare behöver 4 av de 31 händelserna: PreToolUse för begränsningar, PostToolUse för automatiska ändringar, SessionStart för kontext och Stop för slutkontroller.

Händelser inträffar med tre frekvenser: en gång per session (SessionStart, SessionEnd), en gång per tur (UserPromptSubmit, Stop, StopFailure) och vid varje verktygsanrop i den agentiska loopen (PreToolUse, PostToolUse).

— Anthropic, hookreferens, https://code.claude.com/docs/en/hooks

Det är viktigt att förstå de tre frekvenserna så att du inte blir förvirrad:

  1. En gång per session - SessionStart, SessionEnd, Setup. De körs en gång när Claude Code öppnas och stängs. Använd dem för att initiera kontext och rensa tillfälliga filer.
  2. En gång per tur - UserPromptSubmit, UserPromptExpansion, Stop, StopFailure. De utlöses vid vart och ett av dina meddelanden och varje gång modellen stannar. Använd dem för att logga konversationen och göra slutkontroller av resultatet.
  3. Vid varje verktygsanrop - PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, PermissionDenied, PostToolBatch, SubagentStart, SubagentStop. De utlöses vid varje filredigering, Bash-start, läsning eller sökning. Det här är det mest aktiva lagret, där begränsningar, formatering och validering hör hemma.

Den fullständiga listan med 31 händelser omfattar asynkrona/reaktiva händelser: TeammateIdle, PreCompact, PostCompact, Elicitation, ElicitationResult, InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, WorktreeCreate, WorktreeRemove, Notification. De är till för avancerade scenarier; hoppa över dem tills vidare.

Test med fem frågor: när hör en regel hemma i en hook, och när ska den stå kvar i CLAUDE.md?

Det viktigaste

Om en regel gäller en åtgärd (inte kontext), handlar om säkerhet/pengar/data, kan kontrolleras med grep/regex, är svår att åtgärda om den bryts och Claude redan har brutit mot den minst en gång, hör den hemma i en hook. Om färre än tre av de fem svaren är ”ja” står den kvar i CLAUDE.md. Jag använde testet på mina 80 rader och flyttade 5 regler till hooks.

För mig fungerar det här som en lag, inte som ett förslag. Jag kokade ner valet mellan CLAUDE.md och en hook till ett enkelt test med fem frågor. Om minst tre svar är ”ja” ska regeln in i en hook. Om färre än tre svar är ”ja” står den kvar i texten.

Testet ”när hör en regel hemma i en hook?”:

  1. Gäller regeln en åtgärd snarare än kontext? (”kör inte rm -rf” är en åtgärd; ”jag har ett TypeScript-projekt” är kontext.)
  2. Gäller regeln säkerhet, pengar eller data? (”rör inte .env”, ”publicera inte nycklar”.)
  3. Kan du kontrollera den med grep, regex eller exitkoden från ett shell-kommando? (”kontrollera efter redigering av .md att det inte finns något em-dash”.)
  4. Skulle konsekvenserna vara jobbiga att åtgärda om regeln bröts? (”force push till main” är svårt att åtgärda; ”glömde en punkt i slutet” är enkelt.)
  5. Har du redan sett Claude glömma den här regeln minst en gång?

Exempel: ”Använd inte em-dash; använd bara bindestreck.” Åtgärd? Ja. Säkerhet? Nej. Går att kontrollera med grep? Ja. Svårt att åtgärda? Nej. Har du sett den brytas? Ja. 3 av 5: lägg den i en hook.

Motexempel: ”Jag har vibe-kodare, inte utvecklare; skriv utan slang.” Åtgärd? Snarare kontext. Säkerhet? Nej. Går att kontrollera med regex? För svårt. Svårt att åtgärda? Nej. Har du sett den brytas? Ja. 1 av 5: låt den stå kvar i CLAUDE.md.

Fem av mina regler klarade testet. De flyttades till hooks. De resterande 75% blev kvar i textfilen.

7 färdiga hooks att kopiera

Det viktigaste

Nedan finns 7 färdiga hooks för .claude/settings.json: blockera redigeringar av .env, kör prettier automatiskt, förhindra force-push till main, leta efter em-dash, frigör en port innan npm run dev, gör en automatisk commit efter lyckade tester och lägg till aktuell kontext i början av en session. Kopiera varje hook till filen, starta om Claude Code och sedan fungerar det.

Alla 7 hooks finns i en och samma .claude/settings.json-fil i projektets rot. Varje post har en händelsetyp, ett verktygsfilter (matchare) och ett shell-kommando. Exitkod 2 nekar ett matchande PreToolUse-anrop före körning. I PostToolUse har åtgärden redan skett: hooken ger återkoppling och kan inte ångra den.

För de flesta hookhändelser är det bara exitkod 2 som blockerar en åtgärd. Claude Code behandlar exitkod 1 som ett fel som inte blockerar och fortsätter åtgärden, trots att 1 konventionellt betyder fel i Unix.

— Anthropic, hookreferens, https://code.claude.com/docs/en/hooks

exit 0 betyder att det lyckades. exit 1 betyder att ett fel inträffade, men Claude fortsätter (det här är den första antipatternen nedan). exit 2 har händelsespecifikt beteende: det kan blockera PreToolUse, medan PostToolUse bara ger återkoppling om redan utförd åtgärd.

Hook 1. Kontrollera direkta .env-redigeringar (PreToolUse, exit 2)

Blockerar försök att redigera någon fil som matchar .env, .env.local, .env.production och så vidare.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '(^|/)\\.env(\\.|$)' && { echo 'Redigera inte `.env` direkt. Tala om vilken variabel jag ska lägga till, så öppnar jag filen själv.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}

Vad den gör: Före varje Edit/Write/MultiEdit hämtar den filsökvägen, kontrollerar med grep om den matchar .env-mönstret och returnerar i så fall exit 2 med ett meddelande. Claude ser stderr och säger till mig: ”Jag försökte redigera .env, men hooken blockerade mig; tala om vilken variabel jag ska lägga till.”

Hook 2. Kör prettier automatiskt efter varje kodredigering (PostToolUse, exit 0)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx|json)$' && npx prettier --write \"$FILE\" 2>/dev/null & exit 0"
          }
        ]
      }
    ]
  }
}

Vad den gör: Efter varje Edit/Write hämtar den sökvägen och kör prettier i bakgrunden med & om filändelsen är .ts/.tsx/.js/.jsx/.json. Den avslutas alltid med exit 0 eftersom det här är formatering, inte en begränsning. Om prettier misslyckas är det ingen fara; filen förblir som den var.

Skillnaden mot hook 1 är grundläggande: PreToolUse + exit 2 är en hård blockering före åtgärden. PostToolUse + exit 0 är en mjuk ändring efter åtgärden. Det här är två olika lägen för olika uppgifter.

Hook 3. Blockera git push --force till main-grenen (PreToolUse, exit 2)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE 'git[[:space:]]+push' && echo \"$CMD\" | grep -qE '(\\-\\-force|\\-\\-force-with-lease|[[:space:]]-f([[:space:]]|$))' && echo \"$CMD\" | grep -qE '([[:space:]/:])main([[:space:]/:]|$)' && { echo 'Force push till main är förbjudet. Skapa en ny gren eller be mig göra det manuellt.' >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}

Vad den gör: Vid varje Bash-start kontrollerar den att kommandot innehåller push, innehåller --force/--force-with-lease/-f och nämner main. Om alla tre villkor matchar blockeras det med ett tydligt meddelande.

Tidigare hade jag den här regeln i CLAUDE.md med stora bokstäver. Claude bröt ändå mot den ungefär varannan vecka när jag bad den att ”force-pusha för att ångra min dåliga commit”. Hooken fångar de bokstavliga kommandoformerna men alias, skript, implicit målgren eller annan Git-klient kan kringgå strängkontrollen. Upprätthåll main-grenskydd på fjärrservern och begränsa agentens autentiseringsuppgifter.

Hook 4. Blockera em-dash i textfiler (PostToolUse, exit 2)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(md|mdx|tsx?)$' && grep -q $'\\xe2\\x80\\x94' \"$FILE\" && { echo \"Filen $FILE innehåller ett em-dash (U+2014). Ersätt det med ett vanligt bindestreck (U+002D).\" >&2; exit 2; } || exit 0"
          }
        ]
      }
    ]
  }
}

Vad den gör: PostToolUse med exit 2 rapporterar misslyckad efterkontroll till Claude efter redigeringen. Det återställer eller förhindrar inte skrivningen. Om Claude har redigerat en fil och lämnat ett em-dash får hooken ett fel, Claude får återkoppling och försöker igen. Efter 2–3 sessioner slutar Claude helt att infoga det här tecknet.

Observera att grep söker specifikt efter em-dash-tecknet (U+2014) med $'\xe2\x80\x94', Bash-notationen för att skriva det som UTF-8-byte. Om du bara skriver grep -q '-' fångar det vanliga bindestreck och körs för varje fil. Ett osynligt tecken förstör hela logiken.

Hook 5. Frigör port 3000 före npm run dev (PreToolUse, exit 0)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE '(npm|pnpm|yarn)[[:space:]]+run[[:space:]]+dev' && { lsof -ti:3000 | xargs -r kill -9 2>/dev/null; echo 'Port 3000 frigjord före start' >&2; }; exit 0"
          }
        ]
      }
    ]
  }
}

Vad den gör: Före varje npm run dev (eller pnpm/yarn) avslutar den processen på port 3000. Den avslutas alltid med exit 0 eftersom det här är en förberedelse, inte en begränsning. Det skyddar mot en typ av fälla där Claude ser ”porten används”, tror att det är ett kodproblem och börjar åtgärda saker, när den gamla servern i själva verket bara lämnades igång.

Hook 6. Skapa en automatisk commit efter lyckade tester (PostToolUse, exit 0)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "INPUT=$(cat); CMD=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty') || exit 1; EXIT=$(printf '%s' \"$INPUT\" | jq -r '.tool_response.exit_code // empty') || exit 1; case \"$CMD\" in 'npm test'|'pnpm test'|'yarn test') ;; *) exit 0 ;; esac; [ \"$EXIT\" = \"0\" ] || exit 0; git diff --cached --quiet || { echo 'Existing staged changes: review manually' >&2; exit 0; }; git add -- src/orders.ts tests/orders.test.ts || exit 1; git diff --cached --quiet && exit 0; git commit -m 'tests: checkpoint after successful run'"
          }
        ]
      }
    ]
  }
}

Vad den gör: Den läser stdin-JSON en gång och accepterar bara de exakta rena testkommandona som visas. Den fortsätter bara när angivet tool_response.exit_code uttryckligen är noll; om installerad version inte ger fältet hoppar den över commit i stället för att gissa framgång. Ersätt src/orders.ts och tests/orders.test.ts med uppgiftens exakta ägda filer. Befintliga mellanlagrade ändringar stoppar receptet så att orelaterat arbete inte tas med. Misslyckad add stoppar, tom diff skapar ingen commit och vanliga commit-hooks är kvar. Testa lyckad, misslyckad och saknad resultatindata offline före aktivering.

Antimönster: att lägga till git commit --no-verify vid varje Edit. Behåll commit-kontroller aktiverade. Om en kontroll utlöser automationen rekursivt begränsar du utlösaren eller utformar om kontrollen; kringgå inte verifiering som standard. Granska mellanlagrad diff och uppgiftsägarskap före automatisk commit.

Hook 7. Lägg till aktuell kontext vid sessionsstart (SessionStart med additionalContext)

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo 'no-branch'); STATUS=$(git status --short 2>/dev/null | head -10); echo \"{\\\"additionalContext\\\":\\\"Aktuell gren: $BRANCH. Ocommittade ändringar:\\n$STATUS\\\"}\""
          }
        ]
      }
    ]
  }
}

Vad den gör: Vid sessionsstart hämtar den den aktuella Git-grenen och listan över ocommittade ändringar och returnerar sedan JSON med ett additionalContext-fält. Claude Code lägger in den här texten i det första systemmeddelandet, så Claude vet från början var den befinner sig och vad som återstår från föregående session.

Använd additionalContext för information som Claude bör känna till om miljöns aktuella tillstånd eller en nyligen slutförd åtgärd: miljötillstånd (aktuell gren, distributionsmål, aktiva funktionsflaggor), villkorade projektregler (vilket testkommando som gäller för filen som just redigerats), externa data (öppna ärenden som tilldelats dig, senaste CI-resultat).

— Anthropic, hookreferens, https://code.claude.com/docs/en/hooks

Utökningar av den här hooken: hämta den senaste committen och dess beskrivning, GitHub Actions-status, den aktiva sprinten från Linear/Jira och öppna ärenden för det aktuella arkivet.

3 hookfallgropar som drabbar varannan person

Det viktigaste

Den första fallgropen: en hook med exit 1 släpps igenom utan något meddelande, och Claude ignorerar felet. Den andra: tungt arbete i en hook avslutas efter en timeout på 60 sekunder. Den tredje: en hook ser bara miljövariablerna från ~/.zshrc, inte projektets .env. Alla går att åtgärda med en rad, men utan kännedom om fallgroparna kan det verka som att ”hooks inte fungerar”.

Fallgrop 1. En hook med exit 1 släpps igenom utan något meddelande

Den farligaste. I en vecka trodde jag att mitt .env-skydd fungerade, men det visade sig att jag hade jq -r '.tool_input.file_path' i hooken utan att kontrollera om värdet var null. När tool_input var tomt avslutades jq med kod 1. Claude tolkade exit 1 som ”hookfel, fortsätt”, så åtgärden genomfördes. Jag förstod inte varför .env fortfarande påverkades. Jag såg orsaken först när jag körde hooken manuellt i en terminal med tom indata.

Lösningen är en enda regel: börja varje hook med att kontrollera om värdet är null och avsluta den med || exit 0. Då leder alla oväntade exitkoder till en ”säker” avslutning i stället för att passera obemärkt.

# Mall för säker start och avslutning:
FILE=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
# ... din logik ...
exit 0   # eller || exit 0 på en rad

Fallgrop 2. Långsamma hooks avslutas vid timeout

Jag hade en hook som körde eslint --fix på hela filen efter Edit. Det tog 8–10 sekunder att bearbeta stora filer (1000+ rader). Standardtimeouten för hooks i Claude Code är 60 sekunder, så jag trodde att jag höll mig inom gränsen. Men när flera redigeringar sker under en minut samlas hookarna på hög och köas, och vissa hoppas helt enkelt över. Jag insåg det först när loggen visade ”hook timed out”.

Lösning: om en hook kräver mycket arbete ska du köra den i bakgrunden. eslint --fix "$FILE" & utan att vänta på resultatet. Då returnerar hooken exit 0 på några millisekunder medan det verkliga arbetet körs i bakgrunden.

# Tung åtgärd i bakgrunden:
npx eslint --fix "$FILE" &
exit 0

Fallgrop 3. En hook kan inte se projektets miljövariabler

Jag trodde att process.env skulle vara tillgängligt i en hook på samma sätt som i en Node.js-app. Det är det inte. Hooken körs i ett shell som startas av den överordnade Claude Code-processen och ser bara det som finns i ~/.zshrc eller ~/.bashrc, plus det som Claude Code skickar med uttryckligen. Om hooken är beroende av OPENAI_API_KEY från .env måste du läsa .env manuellt.

Lösning: läs uttryckligen .env i hook-kommandot.

# Skicka projektets miljövariabler till hooken:
cd "$PWD" && source .env && curl -H "Authorization: Bearer $OPENAI_API_KEY" ...
# Eller med dotenv:
dotenv -e .env -- node my-validator.js

Det här är särskilt besvärligt i ett monorepo, där olika paket har egna .env-filer. Lös det genom att skicka med PWD och uttryckligen köra cd "$PWD" i början av kommandot.

Hur testar du hooks innan du gör en commit?

Det viktigaste

En hook är ett shell-kommando. Du kan köra det manuellt i en terminal med fejkad stdin, kontrollera dess exitkod och stderr och göra allt detta innan Claude Code kör det. Det sparar timmar av felsökning och skyddar dig mot hooks som blockerar sin egen funktion.

Grundläggande testsituation. Ta hook-kommandot från JSON-filen, öppna en terminal och skicka in den JSON som Claude Code skulle skicka:

# Testa hook 1 (skydda .env):
echo '{"tool_input":{"file_path":".env"}}' | bash -c '
FILE=$(jq -r ".tool_input.file_path // empty")
[ -z "$FILE" ] && exit 0
echo "$FILE" | grep -qE "(^|/)\.env(\.|$)" && {
  echo "BLOCKED" >&2
  exit 2
} || exit 0
'
echo "Exit code: $?"

Du bör se BLOCKED i stderr och Exit code: 2. Om du får exit 0 fungerar inte ditt regex; åtgärda grep.

Scenarier att kontrollera:

  1. Positivt fall - filen ska blockeras. .env för hook 1, git push --force main för hook 3.
  2. Negativt fall - filen ska inte blockeras. README.md för hook 1, git push origin feature-branch för hook 3.
  3. Gränsfall - tom indata, null-värden, oväntade tecken. Hooken ska antingen returnera exit 0 eller avslutas med ett tydligt fel, inte passera obemärkt.

Var du lägger experimentella hooks. Inte i .claude/settings.json, utan i .claude/settings.local.json (inte i git). Lägg till nya hooks där under PostToolUse med echo i stället för den verkliga åtgärden, kontrollera om de utlöses och flytta dem sedan till den produktionsanvända settings.json.

// Experiment i settings.local.json - skriver till /tmp/hook-debug.log:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"[hook fired] $(date) $(jq -r '.tool_input.file_path // \"?\"')\" >> /tmp/hook-debug.log"
          }
        ]
      }
    ]
  }
}

Efter 5–10 redigeringar i Claude Code öppnar du /tmp/hook-debug.log och ser vilka filer som utlöste hooken. Om filen är tom matchar inte matcharen; åtgärda regexet.

Vad händer härnäst?

Det viktigaste

Börja med PostToolUse-hooks som avslutas med exit 0; de är säkrast och blockerar ingenting. Efter en vecka lägger du till din första PreToolUse-hook med exit 2 för att skydda .env eller förhindra force-push. Efter två veckor har CLAUDE.md krympt från ett kontrakt på 200 rader till en kort kontextbeskrivning på 50–80 rader, medan utvalda händelsekontroller flyttats till hooks och hårda åtkomstgränser upprätthålls separat genom behörigheter, sandbox och fjärrskydd.

5 steg för den kommande veckan:

  1. Sök i din CLAUDE.md efter ord som ”aldrig”, ”förbjudet”, ”alltid” och ”måste”. Varje sådan regel kan vara en kandidat för en hook. Om en regel är bindande kommer textfilen inte att upprätthålla den.
  2. Börja med PostToolUse + exit 0. De är säkrast: de gör en ändring men blockerar ingenting. Prettier (hook 2), avsluta utvecklingsservern (hook 5), automatisk commit efter tester (hook 6).
  3. Lägg till din första PreToolUse + exit 2 först när du känner dig säker. Du kan till exempel skydda .env (hook 1) eller förhindra force-push (hook 3). Det här är en hård blockering, och ett misstag i grep-kommandot kan blockera dig själv.
  4. Testa hooks manuellt innan du gör en commit (se avsnittet ovan). Det sparar timmar av felsökning.
  5. Använd settings.local.json för experiment. Lägg till nya hooks där med echo i stället för den verkliga åtgärden.

När dina hooks är konfigurerade är nästa steg i klustret hur du ångrar ändringar i Claude Code, ifall en hook släpper igenom något farligt. Om du vill att Claude ska komma ihåg projektstrukturen mellan sessionerna kan du skapa en andra hjärna i Claude Code (business/-mapp + länkad kunskapsbas). Om du vill ge projektets skills namn så att de kan återanvändas kan du läsa Claude Code Skills.

Om du behöver tillgång till neurala nätverk via Ailoot kan du ta en titt på Ailoot.

Fler detaljer

Frågor om den här guiden: Hooks i Claude Code 2026: steg-för-steg-konfiguration och 7 färdiga exempel

Varför måste hooken läsa JSON-indata bara en gång?

Stdin är en ström: första jq-processen förbrukar den och senare läsningar når EOF. Spara indata en gång och extrahera sedan kommando- och resultatfält ur sparat värde. Behandla saknat eller okänt resultat som overifierat och hoppa över automatisk commit.

Skapar regex-hookarna i exemplen en säkerhetsgräns?

Nej. De kontrollerar specifika händelser och igenkännbara kommandosträngar. Skalomdirigering, alias, skript, MCP eller annan skrivväg kan passera; använd begränsad autentisering, filsystembehörigheter, sandbox och skyddade grenar för hårda gränser.

Alla artiklar