After the summary exists
The conversation is already gone. Compaction has run, Claude Code has replaced it with a summary, and PostCompact fires with that summary in hand. The matcher filters manual or auto, the same split PreCompact uses. The if filter does not apply, and setting it stops the handler from running.
The payload carries trigger and compact_summary, the string compaction produced. The hooks reference gives no schema, size guarantee, or stability contract for that string beyond calling it the generated conversation summary.
Reading it, not shaping it
Compaction is done before your hook runs. No exit code changes the compacted result. On exit 2 the stderr appears to you as a hook-error notice, Claude does not see it, and the compacted conversation stands. There is no decision field. systemMessage and continue are discarded, and while valid JSON is still read on every code, none of it can undo a compaction that has finished. A hook here logs the summary, sends it somewhere, or checks it for a phrase before something downstream depends on it.
You check the summary for the name of the branch you are working on and exit 2 when it is missing, expecting compaction to be rolled back or redone.
The compacted conversation stays. Your stderr shows to you as a hook error, and Claude continues with the summary it has, branch name or not.
If the check decides whether compaction happens, it belongs on PreCompact. If what you want is to put the branch name back in front of Claude, SessionStart fires with a compact source after every compaction, and plain stdout there becomes context.
Module 09 lists this event in its inventory, and module 18 covers compaction from the build side.