Hook event 31 of 33

SessionEnd

Runs while a Claude Code session is terminating and provides a short cleanup window without the ability to stop termination.

Exit code veto
Exit code cannot block
Matchers
clear, resume, logout, prompt_input_exit, other
if filter
Does not apply
Docs checked
2026-08-29

Five reasons, and one that was retired

You are closing a session, clearing it, or switching away with /resume. Before the process disappears, SessionEnd gives your hooks one last shot at cleanup. The matcher filters reason: clear, resume, logout, prompt_input_exit, or other. The payload carries that same reason field. The hooks reference does not say which termination paths land in other.

An earlier value, bypass_permissions_disabled, was removed in v2.1.234.

A short window

Module 09 gives the number: SessionEnd handlers share a 1.5-second budget between them. A hook that flushes a log or closes a connection needs a longer timeout on the handler, needs async, or needs to live somewhere else.

Termination cannot be blocked

No exit code on this event will keep the session alive. Exit 0 lets the session end and discards the output. Exit 1 cannot stop it. Exit 2 cannot stop it either. Its stderr appears to you as a hook-error notice, Claude does not see it, and the session ends. There is no decision field, and JSON output such as systemMessage is discarded.

The mistake most people make first

Your settings carry a matcher of bypass_permissions_disabled from an older configuration, with a cleanup script behind it.

From v2.1.234 that value never arrives in reason, so the handler never runs. The first sign is cleanup that did not happen, some time after the session that should have done it.

Replace the value with one of the five current ones, or drop the matcher so the handler runs on every reason.

Module 18 covers the session lifecycle from the build side.