SessionStart Hook in Claude Code: Run Scripts When a Session Opens
SessionStart is a Claude Code hook that fires once at the beginning of every new session. Use it to cache expensive data fetches, log session start events, or set up session-scoped state that other hooks can read.
When Claude Code starts a new interactive session — or when a headless run begins — it fires the SessionStart hook before the first user message is processed. This makes it the right place to do setup work that would be too slow to repeat on every prompt.
Common uses include: fetching the latest sprint tickets from Jira and writing them to a temp file (so PreToolUse or UserPromptSubmit hooks can read them instantly), recording session start time and git branch to an audit log, or checking that required environment variables are set and alerting the user immediately if they are not.
The hook script receives no special env vars beyond the standard shell environment. It runs in the repository root (the directory where you launched Claude). You can write data to /tmp or to a project-local .claude/cache/ directory for consumption by other hooks.
Unlike UserPromptSubmit which must be fast, SessionStart can afford a second or two since users expect a brief startup delay. Still avoid anything over 5 seconds — wrap long operations in a background process and cache the result asynchronously, then check for the cache file in subsequent hooks.
Examples
#!/bin/bash
# ~/.claude/hooks/session-start.sh
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'not-a-repo')
COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo 'n/a')
USER=$(whoami)
TIMESTAMP=$(date -Iseconds)
echo "[session-start] $TIMESTAMP | user=$USER | branch=$BRANCH | commit=$COMMIT" \
>> ~/.claude/session-log.txt
exit 0#!/bin/bash
# Fetch current sprint tickets and cache to /tmp for use by other hooks
CACHE="/tmp/claude-sprint-context.txt"
if [ -n "$JIRA_API_TOKEN" ] && [ -n "$JIRA_BASE_URL" ]; then
curl -s -u "$JIRA_USER:$JIRA_API_TOKEN" \
"$JIRA_BASE_URL/rest/api/3/search?jql=sprint+in+openSprints()&fields=summary,status&maxResults=20" \
| jq -r '.issues[] | "- [\(.fields.status.name)] \(.fields.summary)"' \
> "$CACHE" 2>/dev/null
fi
exit 0{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/session-start.sh"
}
]
}
]
}
}Tips
- →Write session-scoped cache files to /tmp/claude-<project>-*.txt so they are automatically cleaned on reboot.
- →Use SessionStart to validate required env vars (ANTHROPIC_API_KEY, database URLs) and print clear error messages before the session begins.
- →Combine SessionStart with a Stop hook to compute session duration and log it for time-tracking.
- →If the hook might take more than 2 seconds, spawn the slow work in the background with '& disown' and have it write to a file when done.
- →The hook runs in the project root — you can read CLAUDE.md or package.json to detect project type and set up context accordingly.
FAQ
Does SessionStart run for every new conversation or just once per Claude Code process?+
SessionStart fires at the beginning of each new session — meaning each time you start Claude Code (or each headless invocation). If you start a new conversation within the same running process using /new, it fires again. Think of it as 'once per conversation thread.'
Can I abort a session from the SessionStart hook?+
Yes. If your SessionStart hook exits with a non-zero code and you have 'onError: block' configured, Claude Code will not start the session and will show your hook's stderr output. This is useful for enforcing prerequisites like required env vars or VPN connectivity.
What working directory does the hook run in?+
The hook runs in whatever directory you launched Claude Code from — typically the repository root. The CWD is the same as Claude's working directory for that session.