Claude Code hooks: PreToolUse, PostToolUse, Stop, cu exemple

Claude Code hooks: ce sunt, cum blochezi cu PreToolUse, formatezi cu PostToolUse și verifici la Stop. Referința oficială și cinci exemple rulate.

8 minute de lectură

Claude Code hooks sunt comenzi, cereri HTTP, apeluri de unelte MCP, prompturi sau subagenți pe care Claude Code le execută singur, în puncte fixe ale unei sesiuni. Diferența față de o regulă scrisă în CLAUDE.md este cine o execută: instrucțiunea o citește modelul, care o poate ignora; hook-ul îl rulează harness-ul, de fiecare dată. Mai jos sunt referința oficială, citită la 12 octombrie 2026, și cinci exemple rulate, cu scripturile lor.

Claude Code hooks: instrucțiunea din CLAUDE.md este text în context, hook-ul este cod rulat de Claude Code la un eveniment

Pe scurt
Ce este codul tău, rulat de Claude Code la un eveniment; primește JSON pe stdin
Câte evenimente 33 în referință, la 12 octombrie 2026
Cum blochează ieșire cu codul 2 sau JSON pe stdout, cu ieșire 0
Verificat pe Claude Code 2.1.280; documentația descrie deja 2.1.296

Instrucțiune sau hook: cine execută

Documentația despre memorie spune că fișierele de instrucțiuni sunt tratate „as context, not enforced configuration”, iar pentru o acțiune care trebuie blocată „regardless of what Claude decides” trimite la un hook PreToolUse. Ce scrii în fișierul de instrucțiuni e subiectul ghidului despre AGENTS.md și CLAUDE.md; aici e cealaltă jumătate.

Vrei ca... Pui în
agentul să știe o convenție, o comandă, o capcană instrucțiuni (CLAUDE.md, AGENTS.md)
o unealtă sau o cale să fie refuzată după un tipar fix o regulă de permisiuni
decizia să ceară logică: căi rezolvate, ramura curentă, textul comenzii un hook PreToolUse
ceva să se întâmple după fiecare editare ori la fiecare final de tură un hook PostToolUse sau Stop

Regulile de permisiuni și hook-urile sunt aplicate amândouă de Claude Code, nu de model. Regula e un tipar declarativ pe numele uneltei și pe argumente; hook-ul e un program care primește tot apelul și decide cu logica ta. Se compun asimetric: un hook care refuză bate și o regulă allow, dar un hook care răspunde allow nu trece peste o regulă deny sau ask. Regulile, sandbox-ul și limitele lor sunt în ghidul despre izolarea agenților de programare.

Un hook scris greșit e mai rău decât niciunul, pentru că dă impresia de protecție. Proiectarea unui set de guardrails pe un depozit real, alături de permisiuni, claude -p în CI și MCP, e tratată în modulul „Hooks Lifecycle” al cursului Claude Code Mastery: Coding Agentic din Terminal; cursul lucrează cu evenimentele de bază (PreToolUse, PostToolUse, Stop, SubagentStop) și nu parcurge toată lista de mai jos.

Articolul presupune că folosești deja Claude Code. Alegerea uneltei este în comparația Claude Code vs Cursor vs GitHub Copilot.

Cele 33 de evenimente din referință

Referința le grupează pe trei cadențe: pe sesiune (SessionStart, SessionEnd), pe tură (UserPromptSubmit, Stop, StopFailure) și la fiecare apel de unealtă (PreToolUse, PostToolUse). Ultima coloană spune dacă ieșirea cu codul 2 oprește acțiunea.

Eveniment Când se declanșează Codul 2 blochează?
SessionStart sesiunea începe sau e reluată nu
Setup pornire cu --init-only; cu --init sau --maintenance, în modul -p nu
UserPromptSubmit un prompt e trimis, înainte să ajungă la model da
UserPromptExpansion o comandă tastată se extinde într-un prompt da
PreToolUse înaintea unui apel de unealtă da
PermissionRequest un apel are nevoie de o decizie de permisiune nu; refuzi prin JSON
PermissionDenied auto mode refuză un apel nu
PostToolUse după un apel reușit nu
PostToolUseFailure după un apel eșuat nu
PostToolBatch după un lot de apeluri paralele, înaintea următorului apel la model da
Notification Claude Code trimite o notificare nu
MessageDisplay cât timp se afișează textul răspunsului nu
SubagentStart pornește un subagent nu
SubagentStop un subagent termină da
TaskCreated se creează un task cu TaskCreate da
TaskCompleted un task e marcat ca terminat da
Stop Claude termină de răspuns da
StopFailure tura se încheie cu o eroare de API nu
TeammateIdle un coechipier dintr-un agent team urmează să intre în așteptare da
InstructionsLoaded se încarcă un CLAUDE.md sau un fișier din .claude/rules/ nu
ConfigChange un fișier de configurare se schimbă în timpul sesiunii da
CwdChanged se schimbă directorul de lucru nu
DirectoryAdded se adaugă un director de lucru, de pildă cu /add-dir nu
FileChanged un fișier urmărit se schimbă pe disc nu
WorktreeCreate se creează un worktree da
WorktreeRemove se șterge un worktree creat de un hook da
PreCompact înaintea compactării contextului da
PostCompact după compactare nu
PreModelSwitch înaintea unei schimbări de model cerute da
PostModelSwitch după ce modelul sesiunii s-a schimbat nu
Elicitation un server MCP cere date de la utilizator da
ElicitationResult utilizatorul a răspuns, înainte ca răspunsul să plece la server da
SessionEnd sesiunea se termină nu

Ciclul de viață al hook-urilor Claude Code: SessionStart, UserPromptSubmit, bucla agentului cu PreToolUse și PostToolUse, Stop, Notification și SessionEnd

Patru dintre ele acoperă aproape tot ce cere un dezvoltator:

  • PreToolUse rulează după ce modelul a construit parametrii și înainte de execuție. Se potrivește pe numele uneltei (Bash, Edit, Write, Read, Agent, WebFetch; uneltele MCP se numesc mcp__<server>__<unealtă>) și rulează înaintea verificării de permisiuni: un deny ține și cu --dangerously-skip-permissions.
  • PostToolUse rulează după un apel reușit: poți formata, verifica și trimite feedback, dar nu poți anula.
  • Notification se potrivește pe tipul notificării: permission_prompt după ce o cerere de permisiune a așteptat circa șase secunde, idle_prompt la circa 60 de secunde după ultimul răspuns, dacă n-ai tastat nimic. Nu blochează.
  • Stop rulează la fiecare răspuns încheiat al agentului principal, nu doar la finalul sarcinii, și nu la întreruperea de către utilizator. Poate împiedica oprirea.

Unde se configurează și cum arată în settings.json

Loc Se aplică Se partajează
~/.claude/settings.json în toate proiectele tale nu
.claude/settings.json în proiect da, se comite
.claude/settings.local.json în proiect nu
setări administrate (managed) în toată organizația da, le controlează administratorul
hooks/hooks.json dintr-un plugin cât pluginul e activ da
frontmatter de skill sau de subagent după invocarea skill-ului, respectiv cât rulează subagentul da

Pentru o cheie obișnuită, precedența setărilor din documentație este: administrate, --settings din linia de comandă, local, proiect, utilizator. Hook-urile fac excepție: se adună între niveluri, nu se înlocuiesc, iar același handler definit în mai multe fișiere rulează o singură dată. Frontmatterul unui skill e explicat în articolul despre Agent Skills pentru cod.

Configurația are trei niveluri: evenimentul, grupul cu matcher și lista de handlere.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-bash.py",
            "args": [],
            "timeout": 10
          }
        ]
      }
    ]
  }
}
  • matcher: "*", "" sau absent înseamnă „orice”. Un șir numai din litere, cifre, _, -, spații, , și | e un nume exact sau o listă (Edit|Write); orice alt caracter îl face expresie regulată JavaScript neancorată, deci Edit.* prinde și NotebookEdit. E sensibil la majuscule. Stop și UserPromptSubmit nu au matcher.
  • if: filtru opțional, în sintaxa regulilor de permisiuni (Bash(git *), Edit(*.ts)), doar pe evenimentele de unealtă. Documentația îl numește „best-effort”: când nu poate descompune comanda, hook-ul rulează oricum, deci scriptul verifică singur ce a primit.
  • type: command, http, mcp_tool, prompt sau agent (experimental). Cu args, un hook command pornește executabilul direct, fără shell, și ${CLAUDE_PROJECT_DIR} nu cere ghilimele; fără args, șirul trece printr-un shell și calea se pune între ghilimele duble.
  • timeout: în secunde. Implicit 600 pentru command, http și mcp_tool, 30 pentru prompt, 60 pentru agent; pe UserPromptSubmit, 30.

Hook-urile care se potrivesc rulează în paralel și blochează agentul până termină; "async": true mută un hook command în fundal, unde nu mai poate decide nimic.

Ce primește hook-ul pe stdin

Un obiect JSON. Câmpurile comune: session_id, prompt_id, transcript_path, cwd, permission_mode (nu la toate evenimentele) și hook_event_name. Restul depinde de eveniment:

Eveniment Câmpuri specifice
PreToolUse tool_name, tool_input, tool_use_id
PostToolUse aceleași, plus tool_response și duration_ms
Notification message, title (opțional), notification_type
Stop stop_hook_active, last_assistant_message, background_tasks, session_crons
SessionStart source, opțional model

Pentru Bash, tool_input are command, description, timeout și run_in_background. Pentru Write, Edit și Read, tool_input.file_path e mereu o cale absolută.

Cum răspunde: coduri de ieșire și JSON

Cod de ieșire Sens Efect
0 succes acțiunea continuă; dacă stdout e un singur obiect JSON, câmpurile lui se aplică
2 eroare blocantă pe evenimentele care pot bloca, acțiunea e oprită; stderr e motivul
orice alt cod eroare neblocantă acțiunea continuă; în transcript apare o notă hook error

Capcana clasică: fără JSON valid pe stdout, exit 1 nu blochează nimic.

Varianta cu JSON cere ieșire 0 și un singur obiect pe stdout, fără alt text. Câmpurile universale sunt continue, stopReason, systemMessage și terminalSequence; suppressOutput e acceptat, dar nu are efect. Decizia stă în alt loc la fiecare eveniment:

Eveniment Câmpurile de decizie
PreToolUse hookSpecificOutput.permissionDecision (allow, deny, ask, defer) și permissionDecisionReason; opțional updatedInput, additionalContext
PostToolUse decision: "block" cu reason; opțional additionalContext, updatedToolOutput
Stop decision: "block" cu reason, sau hookSpecificOutput.additionalContext
Notification niciunul
SessionStart doar context: additionalContext sau text simplu pe stdout

La PreToolUse, câmpurile decision și reason de la primul nivel sunt depreciate; vechile valori approve și block corespund lui allow și deny. defer este gândit pentru integrările care rulează claude -p ca subproces și funcționează doar când tura are un singur apel de unealtă. Când mai multe hook-uri răspund diferit, câștigă cel mai restrictiv: deny, defer, ask, allow.

Cinci exemple, rulate

Scripturile stau în .claude/hooks/, executabile (chmod +x); configurația, în .claude/settings.json din proiect. Toate au fost testate izolat, cu JSON pe stdin; patru au rulat și în sesiuni reale claude -p, pe Claude Code 2.1.280.

1. PreToolUse: comenzi Bash refuzate

Configurarea e fragmentul de mai sus. Scriptul refuză rm recursiv în afara proiectului, git push forțat și orice comandă care atinge un fișier .env:

#!/usr/bin/env python3
# PreToolUse pe Bash: refuză rm recursiv în afara proiectului,
# git push forțat și orice comandă care atinge un fișier .env.
import json
import os
import re
import shlex
import sys

data = json.load(sys.stdin.buffer)  # octeți: nu depinde de localizarea shell-ului
command = data.get("tool_input", {}).get("command", "")
cwd = data.get("cwd") or os.getcwd()
root = os.path.realpath(os.environ.get("CLAUDE_PROJECT_DIR") or cwd)


def deny(reason):
    # Ieșire 0 + JSON pe stdout: motivul ajunge la model ca rezultat al apelului
    print(json.dumps({"hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "deny",
        "permissionDecisionReason": reason,
    }}))
    sys.exit(0)


def subcommands(text):
    # Desparte linia la ; && || | & ( ) și întoarce cuvintele fiecărei părți
    lexer = shlex.shlex(text, posix=True, punctuation_chars=True)
    lexer.whitespace_split = True
    try:
        tokens = list(lexer)
    except ValueError:  # ghilimele neînchise, de pildă într-un heredoc
        tokens = text.split()
    part = []
    for token in tokens + [";"]:
        if set(token) <= set(";&|()"):
            if part:
                yield part
            part = []
        else:
            part.append(token)


for words in subcommands(command):
    # Sari peste atribuirile de mediu (VAR=valoare) și peste sudo
    while words and (re.match(r"\w+=", words[0]) or words[0] == "sudo"):
        words = words[1:]
    if not words:
        continue
    program, args = os.path.basename(words[0]), words[1:]

    if program == "rm":
        short_flags = "".join(a[1:] for a in args if re.match(r"-[^-]", a))
        if "r" in short_flags.lower() or "--recursive" in args:
            for target in (a for a in args if not a.startswith("-")):
                path = os.path.realpath(os.path.join(cwd, os.path.expanduser(target)))
                if "$" in target or "`" in target or not path.startswith(root + os.sep):
                    deny(f"rm recursiv refuzat: ținta '{target}' nu este în interiorul "
                         f"proiectului ({root}). Șterge doar căi din proiect sau cere "
                         "utilizatorului să ruleze comanda manual.")

    if program == "git" and "push" in args:
        if any(a.startswith(("--force", "+")) or re.match(r"-[a-zA-Z]*f", a) for a in args):
            deny("git push forțat refuzat de politica proiectului. Creează un commit "
                 "nou sau cere utilizatorului să ruleze comanda manual.")

    for arg in args:
        name = os.path.basename(arg.split("=")[-1])
        if re.fullmatch(r"\.env(\..+)?", name) and name != ".env.example":
            deny(f"Acces refuzat la '{name}': fișierele .env conțin secrete. "
                 "Folosește .env.example pentru numele variabilelor.")

sys.exit(0)  # nicio obiecție: decide fluxul normal de permisiuni

Rulat real, cu Bash permis prin --allowedTools: modelul a cerut rm -rf ../outside/victim, wc -l .env și git push --force-with-lease; toate trei au fost refuzate, iar directorul a rămas neatins. Modelul vede motivul ca rezultat al apelului:

PreToolUse:Bash hook error: git push forțat refuzat de politica proiectului. Creează un commit nou sau cere utilizatorului să ruleze comanda manual.

Testul izolat a acoperit 27 de comenzi și a arătat limita: bash -c "rm -rf /" trece, fiindcă scriptul nu intră în șirul dat altui shell. E o plasă pentru greșeli, nu o graniță. Nu vede nici unealta Read, nici fișierele pomenite cu @ în prompt, care nu trec prin niciun apel de unealtă; pentru ele, documentația cere o regulă deny de tip Read(./.env).

2. PostToolUse: Prettier după fiecare editare

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format-file.sh",
            "args": [],
            "timeout": 30
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# PostToolUse pe Edit|Write: formatează fișierul pe care Claude tocmai l-a scris.
input=$(cat)
file=$(jq -r '.tool_input.file_path // empty' <<<"$input")
[ -f "$file" ] || exit 0

cd "$CLAUDE_PROJECT_DIR" || exit 0

case "$file" in
  *.js|*.jsx|*.ts|*.tsx|*.json|*.css)
    # Aici schimbi comanda pentru alt limbaj: adaugi o ramură cu formatorul lui.
    if ! out=$(npx prettier --write "$file" 2>&1); then
      echo "Prettier nu a putut formata $file:" >&2
      echo "$out" >&2
      exit 2  # unealta a rulat deja; ieșirea 2 doar îi arată lui Claude stderr
    fi
    ;;
esac
exit 0

Cu --write, Prettier rescrie fișierul pe loc. Pentru alt limbaj adaugi o ramură în case, cu formatorul lui; astfel de ramuri nu au fost rulate aici. Rulat real: Claude a scris src/greet.js pe o singură linie, cu apostrofuri, iar pe disc fișierul a ajuns pe trei linii, cu ghilimele duble. Izolat, un fișier cu eroare de sintaxă a produs codul 2 și mesajul Prettier pe stderr, pe care PostToolUse i-l arată lui Claude. Hook-ul nu rulează când fișierul e rescris de o comandă Bash; pentru asta există FileChanged.

3. Notification: află când Claude te așteaptă

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt|idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/notify.sh",
            "args": []
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# Notification: trimite mesajul primit de la Claude Code ca notificare de desktop.
input=$(cat)
message=$(jq -r '.message // "Claude Code are nevoie de tine"' <<<"$input")
title=$(jq -r '.title // "Claude Code"' <<<"$input")

if command -v osascript >/dev/null 2>&1; then
  # macOS: textul intră ca argument, nu lipit în script, deci ghilimelele nu strică nimic
  osascript -e 'on run argv' \
            -e 'display notification (item 1 of argv) with title (item 2 of argv)' \
            -e 'end run' "$message" "$title"
elif command -v notify-send >/dev/null 2>&1; then
  # Linux, cu un serviciu de notificări pornit
  notify-send "$title" "$message"
fi
exit 0

Pentru toate proiectele, fragmentul merge în ~/.claude/settings.json, cu o cale absolută spre script. Testat doar izolat: pe macOS iese cu 0, inclusiv cu ghilimele și apostrof în mesaj; ramura Linux a rulat cu un notify-send simulat, nu cu cel real. Într-o sesiune reală nu s-a declanșat, rulările fiind neinteractive. Dacă pe macOS nu apare nimic, ghidul oficial de hooks explică de ce: osascript trimite notificările prin Script Editor, căruia trebuie să-i permiți notificările.

4. Stop: testele trec sau Claude continuă

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-before-stop.sh",
            "args": [],
            "timeout": 120
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# Stop: rulează testele; dacă pică, Claude primește motivul și continuă.
input=$(cat)
already_continuing=$(jq -r '.stop_hook_active' <<<"$input")

cd "$CLAUDE_PROJECT_DIR" || exit 0

# Arbore git curat: nimic de verificat, deci nu rulăm testele.
if git rev-parse --is-inside-work-tree >/dev/null 2>&1 && [ -z "$(git status --porcelain)" ]; then
  exit 0
fi

if out=$(npm test 2>&1); then
  exit 0  # testele trec: Claude se poate opri
fi
summary=$(tail -n 15 <<<"$out")

if [ "$already_continuing" = "true" ]; then
  # Garda de buclă: a doua oară nu mai blocăm, doar anunțăm omul.
  jq -n --arg m "Testele pică și după o rundă de reparații. Verifică manual." \
    '{systemMessage: $m}'
else
  jq -n --arg r "Testele pică. Repară cauza, nu testul, apoi încheie. Ieșirea npm test:
$summary" '{decision: "block", reason: $r}'
fi
exit 0

stop_hook_active este garda din documentație împotriva buclei: e true când Claude continuă deja din cauza unui hook Stop. Scriptul blochează o singură dată; a doua oară doar anunță omul, prin systemMessage. Fără gardă rămâne plafonul lui Claude Code: după opt continuări consecutive oprirea trece, dar numărătoarea se resetează la fiecare apel de unealtă.

Rulat real: cu un defect lăsat intenționat în src/sum.js, Claude a terminat o sarcină fără legătură și a spus „gata”. Hook-ul a blocat oprirea, modelul a primit motivul cu prefixul Stop hook feedback:, a reparat funcția și s-a oprit din nou; a doua intrare avea stop_hook_active: true, iar testele treceau.

5. SessionStart: context injectat la pornire

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-context.sh",
            "args": []
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# SessionStart: fapte despre mediul local, ca text simplu pe stdout (ajunge în contextul lui Claude).
cd "$CLAUDE_PROJECT_DIR" || exit 0

echo "Versiunea Node activă în acest terminal: $(node -v 2>/dev/null || echo 'node lipsește')."
echo "Ținta de deploy a acestui terminal (DEPLOY_TARGET): ${DEPLOY_TARGET:-nesetată}."
[ -d node_modules ] || echo "Dependențele nu sunt instalate: lipsește node_modules."
exit 0

La SessionStart, textul simplu de pe stdout intră în contextul lui Claude. Documentația recomandă propoziții factuale, nu ordine, și CLAUDE.md pentru ce e static. Rulat real: întrebat fără unelte care e ținta de deploy, modelul a răspuns cu valoarea pe care o avea doar hook-ul. O primă variantă injecta ramura git și era redundantă: Claude Code pune singur în context un instantaneu al stării git. Ce merită să ajungă în context e subiectul cursului Context Engineering și memorie pentru agenți AI.

Depanare

  • /hooks deschide un browser doar-citire al hook-urilor configurate, cu sursa fiecăruia. Dacă hook-ul lipsește de acolo, problema e în fișier: JSON invalid (virgulă la final, comentarii) ori alt director.
  • claude --debug-file <cale> scrie jurnalul într-un loc cunoscut; claude --debug îl scrie în ~/.claude/debug/<session-id>.txt. Ctrl+O deschide transcriptul, unde apar blocările și erorile; un hook reușit nu lasă urme acolo.
  • Testează scriptul singur, cu JSON pe stdin, și citește codul de ieșire cu echo $?.

Greșelile frecvente, toate descrise în documentația oficială: script neexecutabil; cale relativă, care depinde de directorul curent (folosește ${CLAUDE_PROJECT_DIR}); cale cu spații fără ghilimele, în forma cu shell; text tipărit de profilul shell-ului înaintea JSON-ului; permissionDecision pus la primul nivel, unde e ignorat fără eroare; matcher scris cu alte majuscule; hook Stop fără gardă.

Securitate

Un hook command rulează cu toate drepturile contului tău. De aici, trei reguli.

Hook-urile dintr-un depozit sunt cod străin. Într-o sesiune interactivă, Claude Code le reține până accepți dialogul de încredere pentru director. În modul -p și în SDK, dialogul nu apare și directorul e considerat de încredere, deci hook-urile comise în .claude/settings.json rulează. Înainte de a rula claude -p peste un depozit străin, citește-i directorul .claude/ ori pornește cu --bare sau cu --settings '{"disableAllHooks": true}'.

Intrarea e nesigură. Comanda din tool_input poate fi scrisă sub influența unui text ostil citit de agent: pune variabilele între ghilimele și verifică secvențele .. din căi. Apărarea pe straturi e în ghidul despre prompt injection la agenți, iar trust boundaries și sandboxing-ul, în cursul OpenClaw: Securizarea Agenților AI Open-Source în Producție.

Administratorul are ultimul cuvânt. Cu allowManagedHooksOnly în setările administrate, hook-urile de utilizator, de proiect, locale și de plugin sunt blocate; disableAllHooks pus în fișierele tale nu oprește hook-urile administrate.

Ce nu rezolvă un hook

  • Un hook care nu pornește nu blochează. O cale greșită, un script care crapă ori un timeout sunt erori neblocante: acțiunea trece. Remediul, "onFailure": "block", apare în changelog la 2.1.295 și nu a fost rulat aici, pe 2.1.280.
  • Textul unei comenzi nu e o graniță. Pentru restricții de fișiere și de rețea care nu depind de text, documentația trimite la sandbox.
  • PostToolUse nu anulează nimic. Fișierul e scris, comanda a rulat.
  • Hook-ul nu judecă dacă modificarea e corectă. Tipurile prompt și agent pun un model să decidă, deci nu mai sunt deterministe.
  • Nu înlocuiește CI-ul. Rulează acolo unde rulează Claude Code și se poate opri din setări. Ce verifici pe server e în ghidul despre code review și CI cu agenți.
  • Stop nu înseamnă „sarcină terminată”. Rulează la fiecare răspuns, deci testele lente se plătesc la fiecare tură.

Hook-urile din setări rulează și în subagenți; despre subagenți, vezi Haiku 5.5 și Opus 5.5 folosite împreună.

Întrebări frecvente

Ce este un hook în Claude Code? O comandă, o cerere HTTP, un apel de unealtă MCP, un prompt sau un subagent pe care Claude Code îl execută automat la un eveniment al sesiunii. Primește contextul ca JSON și poate întoarce o decizie.

Care este diferența dintre PreToolUse și PostToolUse? PreToolUse rulează înaintea apelului și îl poate refuza, trimite la confirmare ori rescrie. PostToolUse rulează după un apel reușit: adaugă feedback sau context, dar unealta a rulat deja.

De ce nu blochează hook-ul meu, deși iese cu eroare? Probabil iese cu codul 1. Doar codul 2 blochează; orice alt cod diferit de zero e o eroare neblocantă și acțiunea continuă. Alternativa e ieșirea 0 cu un JSON de decizie pe stdout.

Cum evit bucla infinită la un hook Stop? Citești stop_hook_active din intrare și nu mai blochezi când e true. Ca plasă, Claude Code lasă oprirea să treacă după opt continuări consecutive, limită reglabilă cu CLAUDE_CODE_STOP_HOOK_BLOCK_CAP.

Unde pun hook-urile ca să le primească toată echipa? În .claude/settings.json, comis în depozit, cu scripturile referite prin ${CLAUDE_PROJECT_DIR}. Preferințele personale merg în .claude/settings.local.json sau în ~/.claude/settings.json.

Concluzie

Instrucțiunile spun agentului ce ai vrea; hook-urile hotărăsc ce se întâmplă. Patru evenimente acoperă nevoile obișnuite: PreToolUse pentru refuzuri, PostToolUse pentru formatare, Notification pentru atenția ta și Stop pentru verificarea de la final. Contractul e scurt: JSON pe stdin, codul 2 sau un JSON de decizie la ieșire. Greșelile se repetă: exit 1 în loc de 2, o cale care nu pornește, un Stop fără gardă. Un hook rămâne o plasă; granițele sunt permisiunile, sandbox-ul și CI-ul.

Surse

Articol redactat la 12 octombrie 2026. Faptele despre hook-uri vin din documentația oficială citită în aceeași zi; exemplele 1, 2, 4 și 5 au rulat în sesiuni reale cu Claude Code 2.1.280, iar exemplul 3 a fost testat doar izolat, cu intrare simulată. Cursuri-AI.ro este furnizor de cursuri plătite, deci parte interesată.

Autor

Echipa editorială Cursuri AI

Redacție dedicată AI-ului aplicat, în limba română. Actualizăm articolul când se schimbă modelele, prețurile sau legislația la care face referire.

  • Surse oficiale citate în text
  • Revizuit înainte de publicare

Cursul care continuă acest articol

Claude Code Mastery: Coding Agentic din Terminal (multi-fișier, git, CI, MCP)

  • 28 lecții
  • ~25h de conținut
  • IT Pro

Ai citit teoria. În curs o aplici: lecții structurate pe module, exerciții și quiz-uri cu feedback imediat, Profesorul AI integrat în fiecare lecție și progres salvat automat. La final primești o atestare privată de finalizare.

Din programa cursului

  1. Fundamente Claude Code: Instalare, CLI și Modelul Mental Agent-First
  2. Multi-Fișier și Codebase-uri Mari: Plan, Edit, Review și Context Management
  3. Workflow Git Complet: Branch-uri, Commit-uri, Conflicte, Code Review și PR-uri
  4. Headless Mode (claude -p) și Automatizare Non-Interactivă

+ încă 7 module în programa completă

499 lei pe lună pentru acest curs, TVA 21% inclus · sau 1.999 lei pe lună pentru toate cele 25 de cursuri IT Pro (vezi ce înveți în tot parcursul).

Abonament lunar, cu reînnoire automată · anulezi oricând din contul tău · conținut digital cu acces imediat, vezi condițiile de retragere. Atestarea confirmă parcurgerea cursului și este privată — nu este diplomă și nu este calificare recunoscută de stat.

Ți-a plăcut articolul? Lasă o apreciere sau salvează-l pentru mai târziu.
Newsletter

Cele mai recente articole despre AI, pe email

Primești pe loc cele mai recente trei articole și cursul asociat, când există, apoi cel mult trei emailuri în următoarele două săptămâni. Te dezabonezi oricând.

Comunitate

Întrebări & sugestii

Ce au întrebat cititorii despre acest articol — și răspunsurile echipei Cursuri AI.

Mesajele sunt verificate de un moderator înainte de publicare.

Fii primul care lasă o întrebare sau o sugestie pe acest articol.

Catalogul complet

50 de cursuri AI în română, cu exerciții, quiz-uri și Profesor AI

Un curs: 499 lei pe lună · pachet Non-IT: 1.499 lei pe lună · pachet IT Pro: 1.999 lei pe lună. Toate cu TVA inclus, începutul primei lecții se citește fără cont.