De Stop-hook van Claude Code voert een command uit elke keer dat de hoofdagent van Claude klaar is met antwoorden. Daarmee is het de juiste plek om een 'klaar'-notificatie te sturen, het resultaat te loggen of checks te draaien. Hij draait niet als je Claude onderbreekt, en een beurt die eindigt met een API-fout triggert in plaats daarvan StopFailure. De hook krijgt het laatste bericht van Claude binnen in last_assistant_message, en kan decision: "block" teruggeven met een reason om Claude te laten doorgaan in plaats van te stoppen.
01Wanneer Stop wel en niet afgaat
Stop gaat af aan het eind van elke beurt, niet alleen als een hele taak af is. Claude Code heeft geen idee van 'de taak'; het weet alleen dat Claude gestopt is met antwoorden en op jou wacht. Een korte vraag levert één Stop op, en een refactor van twee uur ook.
Hij gaat niet af:
- als je op Esc drukt of de beurt op een andere manier onderbreekt;
- als de beurt eindigt door een API-fout, zoals een rate limit, overbelasting of een mislukte authenticatie. Claude Code triggert daarvoor
StopFailure, met eenerror-veld waarop je kunt matchen (rate_limit,overloaded,server_erroren andere); - als een subagent klaar is. Daarvoor is
SubagentStop.
Stop ondersteunt geen matchers, dus elke matcher die je toevoegt wordt genegeerd en de hook draait bij elke beurt.
02De simpelste Stop hook
Zet dit in ~/.claude/settings.json om een command uit te voeren zodra Claude klaar is. Deze speelt een geluid af op macOS:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
Voor commands voor desktopnotificaties op Mac, Windows en Linux, zie hoe je een notificatie krijgt als Claude Code klaar is. De rest van deze gids gaat over wat je met de input en output van de hook kunt doen.
03Wat de hook ontvangt
Naast de gewone velden (session_id, transcript_path, cwd, permission_mode, hook_event_name) krijgt een Stop-hook:
last_assistant_messageDe tekst van het laatste antwoord van Claude in deze beurtstop_hook_activetrue als Claude al doorgaat omdat een Stop hook heeft geblokkeerdbackground_tasksTaken die nog lopen, zoals background shells of subagentssession_cronsGeplande wake-ups in deze sessie, bijvoorbeeld van /loopGebruik last_assistant_message in plaats van het transcriptbestand te lezen. Het transcript wordt asynchroon geschreven en bevat het laatste bericht misschien nog niet als de hook draait.
background_tasks en session_crons vertellen je of de sessie echt klaar is of alleen gepauzeerd tot achtergrondwerk hem weer wakker maakt. Een notificatiescript kan checken dat beide leeg zijn voordat het 'klaar' meldt.
04Zet het laatste bericht van Claude in de notificatie
Dit script (macOS en Linux, vereist jq) toont het begin van het antwoord van Claude en blijft stil als er nog achtergrondwerk loopt:
#!/bin/bash
# ~/.claude/hooks/notify-done.sh
input=$(cat)
# Skip if background tasks are still in flight
busy=$(jq '(.background_tasks // []) | length' <<<"$input")
[ "$busy" -gt 0 ] && exit 0
msg=$(jq -r '(.last_assistant_message // "Claude has finished")
| gsub("\\s+"; " ") | .[0:140]' <<<"$input")
if command -v osascript >/dev/null 2>&1; then
osascript -e 'on run argv' \
-e 'display notification (item 1 of argv) with title "Claude has finished"' \
-e 'end run' "$msg"
elif command -v notify-send >/dev/null 2>&1; then
notify-send "Claude has finished" "$msg"
fi
exit 0
Sla het op, voer chmod +x ~/.claude/hooks/notify-done.sh uit en laat de hook ernaar verwijzen:
{
"hooks": {
"Stop": [
{
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-done.sh" }]
}
]
}
}
Het script eindigt altijd met exitcode 0. Dat is belangrijk: bij Stop betekent exitcode 2 'niet stoppen', dus een notificatiescript dat per ongeluk met 2 eindigt, laat Claude doorwerken.
05Claude laten doorgaan
Stop is een van de events die kunnen blokkeren. Een stop blokkeren betekent dat Claude doorgaat met de beurt, met jouw reden als volgende instructie. Dat kan op twee manieren:
- eindig met exitcode
2, dan krijgt Claude je stderr als reden; of - eindig met exitcode
0en print JSON:
{
"decision": "block",
"reason": "The test suite is failing. Run npm test, fix the failures, then finish."
}
Als de hook eerder sturing geeft dan een fout meldt, houdt hookSpecificOutput.additionalContext het gesprek ook gaande, maar dan markeert het transcript het als feedback van de Stop hook in plaats van als hookfout:
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
Een eindeloze loop voorkomen
Een Stop hook die altijd blokkeert, laat Claude nooit stoppen. Twee dingen beschermen je:
stop_hook_activeistruezodra Claude al doorgaat vanwege een Stop hook. Check dat en stop vroegtijdig.- Claude Code beëindigt de beurt toch als Stop hooks acht keer achter elkaar hebben geblokkeerd zonder tool call ertussen. Met de environment variable
CLAUDE_CODE_STOP_HOOK_BLOCK_CAPverhoog je die limiet.
Hier is een hook die Claude één keer falende tests laat fixen en het daarna laat stoppen:
#!/bin/bash
# ~/.claude/hooks/tests-must-pass.sh
input=$(cat)
[ "$(jq -r '.stop_hook_active' <<<"$input")" = "true" ] && exit 0
if ! npm test --silent >/dev/null 2>&1; then
jq -nc '{decision: "block", reason: "npm test is failing. Fix the failures, then finish."}'
fi
exit 0
Hooks draaien in de werkmap van de sessie, dus npm test draait in je project. Houd checks snel: standaard mag een command hook tot 600 seconden duren, en Claude wacht erop.
Wil je gewoon dat Claude doorwerkt naar een doel zonder dat je een script schrijft? Dan zet het ingebouwde /goal-commando van Claude Code een Stop hook voor je op die alleen voor die sessie geldt.
06Ook meldingen bij fouten
Omdat API-fouten Stop overslaan, hoor je via een 'klaar'-melding niet dat Claude is gestopt door een rate limit. Zet hetzelfde script onder StopFailure als je dat wél wilt weten. De output en exitcode worden genegeerd, dus het kan alleen melden of loggen. Bij StopFailure bevat last_assistant_message de fouttekst, bijvoorbeeld "API Error: Rate limit reached".
07Waar Claude Code Notifier in past
Een Stop hook die een pushdienst aanroept is prima om te weten dat Claude klaar is terwijl je weg bent. Wil je het zelf bouwen, dan is de ntfy-guide een goed begin.
Claude Code Notifier gebruikt dezelfde Stop hook, met één verschil: hij kan iets terugsturen. De gratis command line tool, ccn, draait op Mac, Windows en Linux en toont zelf een desktopmelding. Koppel je de iPhone- of Android-app, dan krijg je een push 'Claude is klaar' zodra een beurt eindigt, en kun je de volgende instructie op je telefoon typen. ccn geeft die vanuit de Stop hook terug als volgende instructie voor Claude, zodat de sessie doorgaat zonder dat je achter je toetsenbord zit. Heb je in de laatste 30 seconden getypt, dan wordt er niet op de telefoon gewacht. Het is onafhankelijk en niet gemaakt door Anthropic. Zie weglopen bij lange taken voor die workflow en de ccn-pagina om te installeren.
08FAQ
Draait de Stop hook als Claude de hele taak af heeft, of na elk antwoord?
Na elk antwoord. Stop vuurt telkens als de hoofdagent klaar is met antwoorden, hoe groot het verzoek ook is. Check background_tasks en session_crons in de input als je wilt weten of er nog werk gepland staat.
Waarom vuurde mijn Stop hook niet?
Misschien heb je de beurt onderbroken, waardoor Stop wordt overgeslagen, of eindigde die met een API-fout, die in plaats daarvan StopFailure afvuurt. Check anders of /hooks je hook toont en of het script uitvoerbaar is. De troubleshooting-checklist behandelt de rest.
Waarom blijft Claude doorwerken nadat mijn Stop hook heeft gedraaid?
Je hook blokkeert het stoppen, door af te sluiten met code 2 of door decision: "block" te printen. Zorg dat een hook die alleen meldt altijd met 0 afsluit, en dat elke blokkerende hook eerst stop_hook_active checkt.
Hoe krijg ik het laatste antwoord van Claude in de hook?
Lees last_assistant_message uit de JSON op stdin, bijvoorbeeld met jq -r '.last_assistant_message'. Vertrouw niet op transcript_path: het bestand bevat het laatste bericht misschien nog niet als Stop vuurt.
Wat is het verschil tussen Stop en SubagentStop?
Stop is voor de hoofdagent. SubagentStop vuurt als een subagent klaar is, matcht op het agenttype en ondersteunt dezelfde blokkeervelden. Een melding 'Claude is klaar' hoort bij Stop.