Feature request: SessionEnd hook that fires once at actual session close

Resolved 💬 3 comments Opened Mar 16, 2026 by spectator81-png Closed Mar 20, 2026

Problem

The Stop hook fires after every agent turn, not just when the session actually ends (/exit, Ctrl+C). This makes it impossible to reliably run cleanup/save tasks at session end.

Current behavior: Stop fires after every assistant response. A blocking Stop hook creates a loop (block → Claude processes → Stop fires again). A non-blocking Stop hook can't trigger Claude to do work.

Desired behavior: A SessionEnd hook that fires exactly once when the user ends the session, with enough time for Claude to do final work (e.g., 30-120 seconds).

Use case

I built a Claude Code plugin that extracts relational signals from the conversation at session end (analyzing communication style, updating a memory vector). This requires Claude to analyze the conversation — it's an LLM task, not a simple script.

With the current Stop event, I can't reliably trigger this:

  • Blocking command hook: Fires every turn, creates loops
  • Prompt hook: JSON validation failed error on Stop events
  • Marker file workaround: Fires mid-session at turn N, not at session end. Signals from later turns are lost.

A SessionEnd hook with prompt or block support would solve this completely.

Proposed solution

A new hook event (or modification of Stop):

  • Fires once when the session is actually ending
  • Supports prompt type (inject instructions for Claude to follow before close)
  • Or supports command type with block decision (Claude processes the block reason, then session closes)
  • Timeout: configurable, default ~120 seconds

Workaround attempted

Wrote a Stop command hook with a marker file: blocks once at turn N, writes marker, approves on subsequent turns. This works but:

  • Fires mid-session (disruptive)
  • Only captures signals up to turn N, losing the rest
  • Not a real "session end" trigger

Environment

  • Claude Code on Windows 11
  • Plugin uses SessionStart (command, works perfectly) and Stop (broken for this use case)

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗