Created
March 9, 2026 20:23
-
-
Save a5r0n/14544654f0469edab17e0d0157938157 to your computer and use it in GitHub Desktop.
Claude code session memory hook
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Auto-save session learnings to memory on exit | |
| LOG="$HOME/.claude/hooks/session-end-memory.log" | |
| log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG"; } | |
| # Loop prevention — child claude -p session also triggers SessionEnd | |
| LOCK="/tmp/claude-memory-save.lock" | |
| if [ -f "$LOCK" ]; then | |
| PID=$(cat "$LOCK") | |
| if kill -0 "$PID" 2>/dev/null; then | |
| log "Skipping — memory save already running (PID $PID)" | |
| exit 0 | |
| else | |
| log "Removing stale lock (PID $PID no longer running)" | |
| rm -f "$LOCK" | |
| fi | |
| fi | |
| echo $$ > "$LOCK" | |
| trap "rm -f $LOCK" EXIT | |
| # Only fire on explicit /exit | |
| HOOK_INPUT=$(cat) | |
| REASON=$(jq -r '.reason' <<< "$HOOK_INPUT") | |
| if [ "$REASON" != "prompt_input_exit" ]; then | |
| log "Skipping — reason was '$REASON', not prompt_input_exit" | |
| exit 0 | |
| fi | |
| TRANSCRIPT=$(jq -r '.transcript_path' <<< "$HOOK_INPUT") | |
| # Derive memory dir from git root — mirrors Claude's path escaping (/ → -) | |
| PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) | |
| ESCAPED=$(echo "$PROJECT_ROOT" | sed 's|/|-|g') | |
| MEMORY_DIR="$HOME/.claude/projects/$ESCAPED/memory" | |
| if [ ! -d "$MEMORY_DIR" ]; then | |
| log "Skipping — no memory dir at $MEMORY_DIR" | |
| exit 0 | |
| fi | |
| log "Running memory save for $MEMORY_DIR (transcript: $TRANSCRIPT)" | |
| # Pre-extract text content from transcript to avoid token overload when reading raw JSONL. | |
| # Pulls assistant text + user text, truncates to ~8000 chars, saves to a temp summary file. | |
| SUMMARY_FILE=$(mktemp /tmp/claude-session-summary.XXXXXX) | |
| python3 - "$TRANSCRIPT" "$SUMMARY_FILE" <<'PYEOF' | |
| import sys, json | |
| transcript_path = sys.argv[1] | |
| output_path = sys.argv[2] | |
| MAX_CHARS = 8000 | |
| chunks = [] | |
| with open(transcript_path) as f: | |
| for line in f: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| try: | |
| obj = json.loads(line) | |
| except json.JSONDecodeError: | |
| continue | |
| role = obj.get("role", "") | |
| if role not in ("user", "assistant"): | |
| continue | |
| content = obj.get("content", "") | |
| texts = [] | |
| if isinstance(content, list): | |
| for block in content: | |
| if isinstance(block, dict) and block.get("type") == "text": | |
| texts.append(block["text"]) | |
| elif isinstance(content, str): | |
| texts.append(content) | |
| for text in texts: | |
| text = text.strip() | |
| if text: | |
| chunks.append(f"[{role}]: {text}") | |
| full = "\n---\n".join(chunks) | |
| if len(full) > MAX_CHARS: | |
| full = full[:MAX_CHARS] + "\n...[truncated]" | |
| with open(output_path, "w") as f: | |
| f.write(full) | |
| PYEOF | |
| trap "rm -f $LOCK $SUMMARY_FILE" EXIT | |
| claude -p "Review the following session summary and save any useful patterns, tool learnings, architectural insights, or user preferences to $MEMORY_DIR. Update MEMORY.md if needed and create or update topic files for detailed notes. Be concise and avoid duplicating existing entries. | |
| <session_summary> | |
| $(cat "$SUMMARY_FILE") | |
| </session_summary>" \ | |
| --allowedTools "Read,Write,Edit,Glob" | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "$schema": "https://json.schemastore.org/claude-code-settings.json", | |
| "hooks": { | |
| "SessionEnd": [ | |
| { | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "~/.claude/hooks/session-end-memory.sh", | |
| "statusMessage": "Saving session memories..." | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment