SessionStart hook in settings.json never fires
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.logon 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 bywheelgroup) — these are unrelated to user hooks - The
Stophook with the samebash "..."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.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗