SessionEnd hooks do not fire on normal `claude -p` (print mode) exit

Status Open
Maintainer reply None cached
Activity 2 comments · opened Jul 21, 2026

Summary

SessionEnd hooks do not fire when a print-mode session (claude -p "...") exits normally. SessionStart and Stop hooks from the same settings file fire correctly in the same run, so this is specific to SessionEnd.

Environment

  • Claude Code v2.1.216
  • macOS (Darwin 25.5.0, arm64)
  • Reproduced with project-scoped .claude/settings.json hooks (no plugins required)

Reproduction

  1. Create an empty directory with .claude/settings.json:
{
  "hooks": {
    "SessionStart": [{"hooks": [{"type": "command", "command": "touch $CLAUDE_PROJECT_DIR/marker-SessionStart"}]}],
    "Stop":         [{"hooks": [{"type": "command", "command": "touch $CLAUDE_PROJECT_DIR/marker-Stop"}]}],
    "SessionEnd":   [{"hooks": [{"type": "command", "command": "touch $CLAUDE_PROJECT_DIR/marker-SessionEnd"}]}]
  }
}
  1. Run from that directory:
claude -p "reply with exactly: ok"
  1. After the process exits (exit code 0, output ok), list the directory.

Observed: marker-SessionStart and marker-Stop exist; marker-SessionEnd was never created (checked repeatedly after exit).

Expected: per the hooks documentation, SessionEnd should run when the session ends (e.g., reason prompt_input_exit / other), in print mode as well.

Impact

Any plugin or configuration that relies on SessionEnd for resource teardown never runs it for headless sessions. Concrete case: a plugin that spawns a per-session helper daemon and registers a SessionEnd hook to shut it down (the OpenAI Codex companion plugin does exactly this) permanently leaks one daemon process group per claude -p invocation, since the daemon's only lifecycle owner is that hook. On an automation-heavy machine these leaked processes accumulate quickly.

Notes

  • Interactive sessions were not part of this repro; the control condition (SessionStart/Stop firing from the same file) isolates the gap to SessionEnd dispatch in print mode.
  • Related but distinct: #35976 covered SessionEnd behavior on /resume in interactive sessions.

View original on GitHub ↗

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