From 00fa1af898f5e1f73c1f4337fa5f18947aeb2464 Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Thu, 12 Mar 2026 23:51:26 +1300 Subject: [PATCH] Move pre-compact hook into repo with HOOKS.md documentation Hook source of truth now in hooks/, symlinked from ~/.claude/hooks/. HOOKS.md covers what each hook does and installation steps. Co-Authored-By: Claude Opus 4.6 --- HOOKS.md | 50 +++++++++++++++++++++++++++++++++++++ hooks/pre-compact-backup.sh | 30 ++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 HOOKS.md create mode 100755 hooks/pre-compact-backup.sh diff --git a/HOOKS.md b/HOOKS.md new file mode 100644 index 0000000..e0d1dc3 --- /dev/null +++ b/HOOKS.md @@ -0,0 +1,50 @@ +# Hooks + +Claude Code hooks that run automatically in response to events. Source of truth lives here; symlinks point from `~/.claude/hooks/` back to this directory. + +## Available Hooks + +| Hook | Event | File | +|------|-------|------| +| Pre-compact backup | PreCompact (auto + manual) | `hooks/pre-compact-backup.sh` | + +## Pre-compact Backup + +Saves a copy of the session transcript before context compaction so no conversation history is lost. Backs up to `~/.claude/transcript-backups/` with timestamped filenames. Auto-prunes backups older than 30 days. + +**Triggers:** Both auto-compaction (context window full) and manual (`/compact` command). + +**Requires:** `python3` (for JSON parsing from stdin). + +## Installation + +1. Symlink each hook into `~/.claude/hooks/`: + ```bash + mkdir -p ~/.claude/hooks + ln -sf "$(pwd)/hooks/pre-compact-backup.sh" ~/.claude/hooks/pre-compact-backup.sh + ``` + +2. Add the hook configuration to `~/.claude/settings.json`: + ```json + { + "hooks": { + "PreCompact": [ + { + "matcher": "auto", + "hooks": [{ "type": "command", "command": "~/.claude/hooks/pre-compact-backup.sh", "timeout": 15 }] + }, + { + "matcher": "manual", + "hooks": [{ "type": "command", "command": "~/.claude/hooks/pre-compact-backup.sh", "timeout": 15 }] + } + ] + } + } + ``` + +## Adding New Hooks + +1. Create the script in `hooks/` +2. Add an entry to this file +3. Symlink into `~/.claude/hooks/` +4. Add the matcher config to `~/.claude/settings.json` diff --git a/hooks/pre-compact-backup.sh b/hooks/pre-compact-backup.sh new file mode 100755 index 0000000..999f4e3 --- /dev/null +++ b/hooks/pre-compact-backup.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Pre-compact hook: save a copy of the session transcript before compaction. +# Claude Code pipes JSON to stdin with session_id, transcript_path, cwd, etc. + +set -euo pipefail + +# Parse stdin JSON for transcript path and session ID +INPUT="$(cat)" +TRANSCRIPT_PATH="$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('transcript_path',''))" 2>/dev/null)" +SESSION_ID="$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id',''))" 2>/dev/null)" +TRIGGER="$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('trigger','unknown'))" 2>/dev/null)" + +if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then + echo "pre-compact-backup: no transcript found, skipping" >&2 + exit 0 +fi + +# Save to ~/.claude/transcript-backups/ with timestamp +BACKUP_DIR="$HOME/.claude/transcript-backups" +mkdir -p "$BACKUP_DIR" + +TIMESTAMP="$(date +%Y%m%d-%H%M%S)" +BACKUP_FILE="${BACKUP_DIR}/${TIMESTAMP}-${SESSION_ID:0:8}-${TRIGGER}.jsonl" + +cp "$TRANSCRIPT_PATH" "$BACKUP_FILE" + +# Prune backups older than 30 days +find "$BACKUP_DIR" -name "*.jsonl" -mtime +30 -delete 2>/dev/null || true + +echo "pre-compact-backup: saved $(wc -l < "$BACKUP_FILE") lines to ${BACKUP_FILE##*/}" >&2