SessionStart hook in settings.json never fires

Resolved 💬 3 comments Opened Mar 26, 2026 by OnyxynO Closed Mar 26, 2026

Description

The SessionStart hook defined in ~/.claude/settings.json never executes, regardless of the configuration variant tried.

Context

Attempting to implement session time tracking: record start time on session open, calculate duration on session close, append to a CSV log. The Stop hook works correctly. SessionStart never fires.

Environment

  • Claude Code CLI version: 50.13.2
  • OS: macOS 25.4.0 (Darwin)
  • Shell: zsh

Configurations tried (all failed)

Attempt 1 — no matcher:

"SessionStart": [
  {
    "hooks": [{ "type": "command", "command": "/path/to/script.sh" }]
  }
]

Attempt 2 — matcher with pipe-separated values (as used by Vercel plugin):

"SessionStart": [
  {
    "matcher": "startup|resume|clear|compact",
    "hooks": [{ "type": "command", "command": "/path/to/script.sh" }]
  }
]

Attempt 3 — matcher with single value:

"SessionStart": [
  {
    "matcher": "startup",
    "hooks": [{ "type": "command", "command": "/path/to/script.sh" }]
  }
]

Attempt 4 — path with spaces escaped:

"command": "/Users/seb/Documents/Claude\\ projet/infra/log-session-start.sh"

Attempt 5 — path with spaces quoted via bash:

"command": "bash \"/Users/seb/Documents/Claude projet/infra/log-session-start.sh\""

Attempt 6 — wrapper script at path without spaces:

"command": "/Users/seb/claude-hooks/session-start.sh"

Diagnostic evidence

  • The script executes correctly when run manually from the terminal
  • Added a debug line to write to /tmp/claude_hook_debug.log on every run
  • The log file was never created by a hook invocation (only by manual runs)
  • Claude Code creates its own UUID-named session files in /tmp/ (owned by wheel group) — these are unrelated to user hooks
  • The Stop hook with the same bash "..." command format does work and writes entries to the CSV log

Expected behavior

SessionStart hook should execute the specified command when a new Claude Code session starts (source: startup), just like Stop hooks execute when a session ends.

Actual behavior

The hook command is never executed. No file is created, no log entry appears.

Workaround

Using a zsh shell function wrapper around the claude command to record start/stop times natively, bypassing the hook system entirely:

function claude() {
  local START_TIME=$(date +%s)
  command claude "$@"
  # calculate duration and append to CSV on exit
}

Additional notes

The Vercel Claude Code plugin uses SessionStart hooks in its own hooks.json (part of the plugin infrastructure) and those do fire correctly. The difference may be that plugin hooks are processed differently from user settings.json hooks — if so, this should be documented.

View original on GitHub ↗

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