Build This Now
Build This Now
Keyboard ShortcutsStatus Line Guide
speedy_devvkoen_salo
Blog/Toolkit/Hooks/Claude Code Session Hooks

Claude Code Session Hooks

Four Claude Code session lifecycle hooks: run init on demand, inject project context on SessionStart, back up transcripts, and log cleanup on SessionEnd exit.

Stop configuring. Start building.

SaaS builder templates with AI orchestration.

Published Feb 12, 2026Toolkit hubHooks index

Problem: Every new session starts blind. You re-explain the branch you're on, the task queue, and the env vars your scripts need. When the session ends, cleanup that should happen never does.

Quick Win: Paste this into settings.json. Git context lands in the chat on every start:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo '## Git' && git branch --show-current && git status --short | head -10"
          }
        ]
      }
    ]
  }
}

Every session now opens with context. Zero manual setup.

The Four Session Lifecycle Hooks

Session behaviour is driven by four hook types:

HookWhen It FiresCan Block?Use Case
SetupWith --init or --maintenanceNOOne-time setup, migrations
SessionStartEvery session start/resumeNOLoad context, set env vars
PreCompactBefore context compactionNOBackup transcripts
SessionEndSession terminatesNOCleanup, logging

SessionStart: Load Context Every Time

SessionStart runs whenever a session begins or resumes. Reach for it when something should always be in Claude's head.

Basic Context Injection

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo '## Project State' && cat .claude/tasks/session-current.md 2>/dev/null || echo 'No active session'"
          }
        ]
      }
    ]
  }
}

With JSON Output

For structured context injection:

#!/usr/bin/env python3
import json
import sys
import subprocess
 
def get_project_context():
    try:
        branch = subprocess.check_output(
            ['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
            text=True, stderr=subprocess.DEVNULL
        ).strip()
        status = subprocess.check_output(
            ['git', 'status', '--porcelain'],
            text=True, stderr=subprocess.DEVNULL
        ).strip()
        changes = len(status.split('\n')) if status else 0
    except:
        branch, changes = "unknown", 0
 
    return f"""=== SESSION CONTEXT ===
Git Branch: {branch}
Uncommitted Changes: {changes}
=== END ===""".strip()
 
output = {
    "hookSpecificOutput": {
        "hookEventName": "SessionStart",
        "additionalContext": get_project_context()
    }
}
print(json.dumps(output))
sys.exit(0)

SessionStart Matchers

Target specific session events:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [{ "type": "command", "command": "echo 'Fresh session'" }]
      },
      {
        "matcher": "resume",
        "hooks": [{ "type": "command", "command": "echo 'Resumed session'" }]
      },
      {
        "matcher": "compact",
        "hooks": [{ "type": "command", "command": "echo 'Post-compaction'" }]
      }
    ]
  }
}
  • startup - New session
  • resume - From --resume, --continue, or /resume
  • clear - After /clear
  • compact - After compaction

Persist Environment Variables

SessionStart has access to CLAUDE_ENV_FILE for setting session-wide environment variables:

#!/bin/bash
 
# Persist environment changes from nvm, pyenv, etc.
ENV_BEFORE=$(export -p | sort)
 
# Setup commands that modify environment
source ~/.nvm/nvm.sh
nvm use 20
 
if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi
 
exit 0

Anything written to CLAUDE_ENV_FILE shows up in every bash command Claude runs after that.

Setup: One-Time Operations

Setup hooks only run when you explicitly invoke --init, --init-only, or --maintenance. Good for work you don't want fired on every new session.

When to Use Setup vs SessionStart

OperationUse SetupUse SessionStart
Install dependenciesYesNo
Run database migrationsYesNo
Load git statusNoYes
Set environment variablesYesYes
Inject project contextNoYes
Cleanup temp filesYes (maintenance)No

Setup Configuration

{
  "hooks": {
    "Setup": [
      {
        "matcher": "init",
        "hooks": [
          {
            "type": "command",
            "command": "npm install && npm run db:migrate"
          }
        ]
      },
      {
        "matcher": "maintenance",
        "hooks": [
          {
            "type": "command",
            "command": "npm prune && npm dedupe && rm -rf .cache"
          }
        ]
      }
    ]
  }
}

Invoke with:

claude --init          # Runs 'init' matcher
claude --init-only     # Runs 'init' matcher, then exits
claude --maintenance   # Runs 'maintenance' matcher

Setup hooks can also write to CLAUDE_ENV_FILE for persisting environment variables.

PreCompact: Before Context Loss

PreCompact fires just before compaction, whether the user triggered it with /compact or it kicked off automatically as the window filled up.

Backup Transcripts

#!/usr/bin/env python3
import json
import sys
import shutil
from pathlib import Path
from datetime import datetime
 
input_data = json.load(sys.stdin)
transcript_path = input_data.get('transcript_path', '')
trigger = input_data.get('trigger', 'unknown')
 
if transcript_path and Path(transcript_path).exists():
    backup_dir = Path('.claude/backups')
    backup_dir.mkdir(parents=True, exist_ok=True)
 
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    backup_name = f"transcript_{trigger}_{timestamp}.jsonl"
    shutil.copy2(transcript_path, backup_dir / backup_name)
 
    # Keep only last 10 backups
    backups = sorted(backup_dir.glob('transcript_*.jsonl'))
    for old_backup in backups[:-10]:
        old_backup.unlink()
 
sys.exit(0)

PreCompact Matchers

{
  "hooks": {
    "PreCompact": [
      {
        "matcher": "auto",
        "hooks": [{ "type": "command", "command": "echo 'Auto-compacting...'" }]
      },
      {
        "matcher": "manual",
        "hooks": [{ "type": "command", "command": "echo 'Manual /compact'" }]
      }
    ]
  }
}
  • auto - Context window filled, automatic compaction
  • manual - User ran /compact

Create Recovery Markers

Pair PreCompact with SessionStart to rebuild context after a compaction event. A working pattern: a shared backup-core module, a statusline monitor that fires threshold-based triggers, and a PreCompact handler, all coordinating through one state file so nothing gets dropped between sessions. See the Context Recovery Hook guide for the full walkthrough.

SessionEnd: Cleanup

SessionEnd runs when a session is on its way out. It can't block the shutdown, but it can do cleanup.

Log Session Stats

#!/usr/bin/env python3
import json
import sys
from pathlib import Path
from datetime import datetime
 
input_data = json.load(sys.stdin)
session_id = input_data.get('session_id', 'unknown')
reason = input_data.get('reason', 'unknown')
 
log_dir = Path('.claude/logs')
log_dir.mkdir(parents=True, exist_ok=True)
 
log_entry = {
    "session_id": session_id,
    "ended_at": datetime.now().isoformat(),
    "reason": reason
}
 
with open(log_dir / 'session-history.jsonl', 'a') as f:
    f.write(json.dumps(log_entry) + '\n')
 
sys.exit(0)

SessionEnd Reasons

The reason field tells you why the session ended:

  • clear - User ran /clear
  • logout - User logged out
  • prompt_input_exit - User exited while prompt was visible
  • other - Other exit reasons

Complete Lifecycle Example

A full lifecycle configuration wired end-to-end:

{
  "hooks": {
    "Setup": [
      {
        "matcher": "init",
        "hooks": [
          {
            "type": "command",
            "command": "npm install && echo 'Dependencies installed'"
          }
        ]
      }
    ],
 
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo '## Context' && git status --short && echo '## Tasks' && cat .claude/tasks/session-current.md 2>/dev/null | head -20"
          }
        ]
      }
    ],
 
    "PreCompact": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "cp \"$CLAUDE_TRANSCRIPT_PATH\" .claude/backups/last-transcript.jsonl 2>/dev/null || true"
          }
        ]
      }
    ],
 
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo \"Session ended: $(date)\" >> .claude/logs/sessions.log"
          }
        ]
      }
    ]
  }
}

Input Payloads

SessionStart Input

{
  "session_id": "abc123",
  "hook_event_name": "SessionStart",
  "source": "startup",
  "model": "claude-sonnet-4-20250514",
  "cwd": "/path/to/project"
}

Setup Input

{
  "session_id": "abc123",
  "hook_event_name": "Setup",
  "trigger": "init",
  "cwd": "/path/to/project"
}

PreCompact Input

{
  "session_id": "abc123",
  "hook_event_name": "PreCompact",
  "transcript_path": "~/.claude/projects/.../transcript.jsonl",
  "trigger": "auto",
  "custom_instructions": ""
}

SessionEnd Input

{
  "session_id": "abc123",
  "hook_event_name": "SessionEnd",
  "reason": "clear",
  "cwd": "/path/to/project"
}

Best Practices

  1. Keep SessionStart fast - It runs on every session. Push heavy work into Setup.

  2. Use Setup for one-time work - Dependency installs, migrations, first-time project bootstrap.

  3. Back up before compaction - PreCompact is your last chance to grab the transcript.

  4. Log session ends - SessionEnd is handy for analytics and debugging.

  5. Match carefully - Different behaviour for startup vs resume vs compact avoids surprises.

Next Steps

  • Set up the main Hooks Guide for all 12 hooks
  • Configure Context Recovery for compaction survival
  • Use Stop Hooks for task enforcement
  • Explore Skill Activation for automatic skill loading

Continue in Hooks

  • Claude Code Setup Hooks
    Braid scripts, agents, and docs into Claude Code setup hooks. One command runs a deterministic script, hands output to a diagnosing agent, logs living docs.
  • Context Backup Hooks for Claude Code
    A StatusLine-driven Claude Code context backup hook. Writes structured snapshots every 10K tokens so auto-compaction never eats errors, signatures, decisions.
  • Cross-Platform Hooks for Claude Code
    Cross-platform Claude Code hooks: skip .cmd, .sh, and .ps1 wrappers and invoke node directly so one .mjs file runs on macOS, Linux, and Windows across the team.
  • Hooks Guide
    Claude Code hooks from first principles: exit codes, JSON output, async commands, HTTP endpoints, PreToolUse and PostToolUse matchers, production patterns.
  • MCP Tool Hooks in Claude Code
    How to call MCP server tools directly from Claude Code hooks using type: mcp_tool — schema, substitution syntax, use cases, and production patterns.
  • Claude Code Permission Hook
    Install a three-tier Claude Code permission hook: instant allow for safe calls, instant deny for dangerous ones, LLM check for the gray area. No skip flag.

More from Toolkit

  • Keyboard Shortcuts
    Configure Claude Code keybindings.json: 17 contexts, keystroke syntax, chord sequences, modifier combinations, and how to unbind any default shortcut instantly.
  • Status Line Guide
    Set up a Claude Code status line for model name, git branch, session cost, and context usage. settings.json config, JSON input, bash, Python, Node.js scripts.
  • AI SEO and GEO Optimization
    A rundown of Generative Engine Optimization: how to get content cited inside ChatGPT, Claude, and Perplexity responses instead of just ranked on Google.
  • Claude Code vs Bolt.new: Which Should You Use?
    Bolt.new prototypes in 28 minutes with zero setup. Claude Code takes 90 minutes but ships production-ready code. Here is how to pick the right tool.

Stop configuring. Start building.

SaaS builder templates with AI orchestration.

On this page

The Four Session Lifecycle Hooks
SessionStart: Load Context Every Time
Basic Context Injection
With JSON Output
SessionStart Matchers
Persist Environment Variables
Setup: One-Time Operations
When to Use Setup vs SessionStart
Setup Configuration
PreCompact: Before Context Loss
Backup Transcripts
PreCompact Matchers
Create Recovery Markers
SessionEnd: Cleanup
Log Session Stats
SessionEnd Reasons
Complete Lifecycle Example
Input Payloads
SessionStart Input
Setup Input
PreCompact Input
SessionEnd Input
Best Practices
Next Steps

Stop configuring. Start building.

SaaS builder templates with AI orchestration.