#!/usr/bin/env bash # session-end.sh — SessionEnd hook # Fires at session end; appends a journal entry to the vault's daily log. # Exit code is ignored by Claude Code — used for cleanup/logging only. # Capture stdin STDIN=$(cat) # Extract fields SESSION_ID=$(echo "$STDIN" | jq -r '.session_id // empty') REASON=$(echo "$STDIN" | jq -r '.reason // empty') # Guard: if SESSION_ID is empty, use PID as fallback to ensure unique path if [ -z "$SESSION_ID" ]; then SESSION_ID="unknown-$$" fi # Parse config with python3+PyYAML; handles missing file, missing key, and tilde VAULT_PATH=$(python3 -c ' import yaml, os p = os.path.expanduser("~/.claude/plugins/memory/config.yaml") cfg = (yaml.safe_load(open(p)) or {}) if os.path.exists(p) else {} print(os.path.expanduser(cfg.get("vault_path", "~/Documents/SecondBrain"))) ') # Compute journal path JOURNAL_DIR="$VAULT_PATH/journal" JOURNAL_PATH="$JOURNAL_DIR/$(date -u +%Y-%m-%d).md" # Ensure journal directory exists mkdir -p "$JOURNAL_DIR" # Create journal note if absent (minimal frontmatter) if [[ ! -f "$JOURNAL_PATH" ]]; then cat >"$JOURNAL_PATH" <<'EOF' --- summary: Daily session log tags: [scope/global, type/log] --- EOF fi # Read touched paths from temp file TOUCH_FILE="/tmp/claude-vault-touched-$SESSION_ID" TOUCHED=$(cat "$TOUCH_FILE" 2>/dev/null || echo "") # Format touched paths list if [[ -z "$TOUCHED" ]]; then TOUCHED_LIST="(none)" else TOUCHED_LIST="$TOUCHED" fi # Determine project context PROJECT=$(git rev-parse --show-toplevel 2>/dev/null || echo "${CLAUDE_PROJECT_DIR:-unknown}") # UTC timestamp for entry header TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) # Append journal entry { echo "" echo "## Session — $TIMESTAMP" echo "" echo "**Project:** $PROJECT" echo "**Reason:** $REASON" echo "**Vault notes touched:**" echo "$TOUCHED_LIST" } >> "$JOURNAL_PATH" # Cleanup temp file rm -f "$TOUCH_FILE" # ── memsearch memory git sync (fail-safe) ─────────────────────────────────── # Auto-commit + push the memsearch memory store (~/.memsearch) on session end. # We own this sync here so we never patch the marketplace memsearch plugin # (which clobbers on update). The whitelist .gitignore in ~/.memsearch tracks # only memory/*.md + .gitignore, so `add -A` is safe. # # Robustness contract: this block MUST NEVER fail session shutdown. # - Skips silently if ~/.memsearch is not a git repo. # - Only commits when something is staged (no empty commits). # - Push is hard-timeboxed and failures are swallowed; the next session's # hook catches up since the daily memory files are append-only. # Wrapped in a subshell with `|| true` so nothing here can abort the hook. ( MEMSEARCH_DIR="/home/jared/.memsearch" if [ -d "$MEMSEARCH_DIR/.git" ]; then git -C "$MEMSEARCH_DIR" add -A >/dev/null 2>&1 if ! git -C "$MEMSEARCH_DIR" diff --cached --quiet; then git -C "$MEMSEARCH_DIR" commit -m "memsearch: session memory $(date +%F)" >/dev/null 2>&1 echo "[session-end] memsearch memory committed" fi timeout 30 git -C "$MEMSEARCH_DIR" push --quiet origin main >/dev/null 2>&1 || true fi ) || true # ──────────────────────────────────────────────────────────────────────────── exit 0