Automation
Hook
Hooks
What maxdmyers-recall runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Where it lives
- hooks/capture-session.shGitHub
Read the script
#!/usr/bin/env bash # recall :: capture hook (Stop) # Idempotent per session_id. Writes a lightweight markdown dump pointing at the # full transcript, so the nightly distill can read sessions without an LLM call here. # Pure shell + jq + git. Never blocks Claude: always exits 0. set -uo pipefail VAULT="${RECALL_VAULT:-$HOME/Documents/Vault/recall}" SESSIONS="$VAULT/sessions" # Don't capture distill's own headless claude runs (recursion guard). [ -n "${RECALL_DISTILL:-}" ] && exit 0 PAYLOAD=$(cat) # Only act on the Stop event regardless of how we're wired. EVENT=$(printf '%s' "$PAYLOAD" | jq -r '.hook_event_name // empty') [ "$EVENT" = "Stop" ] || { [ -n "$EVENT" ] && exit 0; } SESSION_ID=$(printf '%s' "$PAYLOAD" | jq -r '.session_id // empty') CWD=$(printf '%s' "$PAYLOAD" | jq -r '.cwd // empty') TRANSCRIPT=$(printf '%s' "$PAYLOAD" | jq -r '.transcript_path // empty') LAST_MSG=$(printf '%s' "$PAYLOAD" | jq -r '.last_assistant_message // empty' | tr '\n' ' ' | head -c 500) [ -n "$SESSION_ID" ] || exit 0 [ -n "$CWD" ] || CWD="$PWD" # Project identity: git repo root basename, else launch-dir basename. if REPO=$(git -C "$CWD" rev-parse --show-toplevel 2>/dev/null); then PROJECT=$(basename "$REPO") BRANCH=$(git -C "$CWD" rev-parse --abbrev-ref HEAD 2>/dev/null) DIFFSTAT=$(git -C "$CWD" diff --stat 2>/dev/null | tail -40) STATUS=$(git -C "$CWD" status --short 2>/dev/null | head -40) else PROJECT=$(basename "$CWD") BRANCH="" DIFFSTAT="" STATUS="" fi SHORT="${SESSION_ID:0:8}" mkdir -p "$SESSIONS" # One dump per session_id — NOT per project. A session that changes git-repo # context mid-run (cd into another repo, or a repo rename) would otherwise fork # into a second dump and get distilled twice. Reuse the existing dump if one # already exists for this session_id, keeping its original project-named file. FILE="" for existing in "$SESSIONS"/*__"$SHORT".md; do [ -f "$existing" ] || continue if grep -q "^session_id: $SESSION_ID\$" "$existing"; then FILE="$existing"; break; fi done [ -n "$FILE" ] || FILE="$SESSIONS/${PROJECT}__${SHORT}.md" # Preserve original Started timestamp across turns. NOW=$(date -u '+%Y-%m-%dT%H:%M:%SZ') if [ -f "$FILE" ]; then STARTED=$(grep -m1 '^started:' "$FILE" | sed 's/^started: //') fi [ -n "${STARTED:-}" ] || STARTED="$NOW" { echo "---" echo "session_id: $SESSION_ID" echo "project: $PROJECT" echo "branch: ${BRANCH:-}" echo "cwd: $CWD" echo "started: $STARTED" echo "updated: $NOW" echo "transcript: $TRANSCRIPT" echo "distilled: false" echo "tags: [recall/session]" echo "---" echo echo "# Session $SHORT — $PROJECT" echo echo "## Last assistant message" echo "${LAST_MSG:-(none)}" echo echo "## Git diff --stat" echo '```' echo "${DIFFSTAT:-(no repo / no changes)}" echo '```' echo echo "## Git status" echo '```' echo "${STATUS:-(no repo / clean)}" echo '```' } > "$FILE.tmp" && mv "$FILE.tmp" "$FILE" exit 0 - hooks/inject-knowledge.shGitHub
Read the script
#!/usr/bin/env bash # recall :: SessionStart hook — injects vault knowledge indexes as # additional context. Read-only, never modifies project files. Fails open: # any error -> exit 0 silently so a broken vault never blocks session start. # # Fires on all SessionStart sources (startup, resume, clear, compact) so # context survives /clear and auto-compact too. set +e # never die VAULT="${RECALL_VAULT:-$HOME/Documents/Vault/recall}" KNOWLEDGE="$VAULT/knowledge" [ -d "$KNOWLEDGE" ] || exit 0 # Project = git root basename, else cwd basename. Matches capture-session.sh. CWD="$PWD" if REPO=$(git -C "$CWD" rev-parse --show-toplevel 2>/dev/null); then PROJECT=$(basename "$REPO") SKILLS_DIR="$REPO/.claude/skills" else PROJECT=$(basename "$CWD") SKILLS_DIR="$CWD/.claude/skills" fi # Compose the context block. Tagged so Claude can recognize it as background. CTX=$( echo "<recall-knowledge>" echo "Indexes from your knowledge vault. Read full notes on demand:" echo " $KNOWLEDGE/global/<name>.md or $KNOWLEDGE/projects/$PROJECT/<name>.md" echo [ -f "$KNOWLEDGE/global/INDEX.md" ] && { cat "$KNOWLEDGE/global/INDEX.md"; echo; } PROJ_INDEX="$KNOWLEDGE/projects/$PROJECT/INDEX.md" if [ -f "$PROJ_INDEX" ]; then cat "$PROJ_INDEX"; echo else echo "# $PROJECT knowledge" echo "(no project notes yet — distill will populate as sessions accumulate)" echo fi # Live list of installed skills for this project (read each SKILL.md's # frontmatter description). Computed live so newly-installed skills appear # the same day, without waiting on a vault refresh. if [ -d "$SKILLS_DIR" ]; then found=0 for sk in "$SKILLS_DIR"/*/SKILL.md; do [ -f "$sk" ] || continue [ "$found" -eq 0 ] && { echo "# $PROJECT installed skills"; found=1; } sk_name=$(basename "$(dirname "$sk")") sk_desc=$(awk '/^description:/ {sub(/^description: */,""); print; exit}' "$sk") echo "- $sk_name — ${sk_desc:-<no description>}" done [ "$found" -eq 1 ] && echo fi ) # Enforce an injection budget so a large index can't bloat every session's # context. Mirrors Claude Code's auto-memory cap — first 200 lines OR 25KB, # whichever comes first. Truncates on whole-line boundaries (keeps valid UTF-8) # and leaves a visible marker, so nothing is dropped silently. MAX_LINES="${RECALL_INJECT_MAX_LINES:-200}" MAX_BYTES="${RECALL_INJECT_MAX_BYTES:-25600}" truncated=0 if [ "$(printf '%s\n' "$CTX" | wc -l | tr -d ' ')" -gt "$MAX_LINES" ]; then CTX="$(printf '%s\n' "$CTX" | head -n "$MAX_LINES")"; truncated=1 fi while [ "$(printf '%s' "$CTX" | wc -c | tr -d ' ')" -gt "$MAX_BYTES" ] \ && [ "$(printf '%s\n' "$CTX" | wc -l | tr -d ' ')" -gt 1 ]; do CTX="$(printf '%s\n' "$CTX" | sed '$d')"; truncated=1 done # Read the hook event name from stdin (SessionStart for all sources). Default # to SessionStart if stdin parse fails — the value is informational only. STDIN_JSON=$(cat 2>/dev/null) EVENT=$(printf '%s' "$STDIN_JSON" | jq -r '.hook_event_name // "SessionStart"' 2>/dev/null) [ -z "$EVENT" ] && EVENT="SessionStart" # Emit JSON with additionalContext. jq -R -s --arg handles all escaping safely. { printf '%s\n' "$CTX" [ "$truncated" -eq 1 ] && printf '… recall: knowledge index truncated to the injection budget (%s lines / %s bytes). Read the full indexes on demand under %s\n' "$MAX_LINES" "$MAX_BYTES" "$KNOWLEDGE" echo "</recall-knowledge>" } | jq -R -s --arg event "$EVENT" \ '{hookSpecificOutput: {hookEventName: $event, additionalContext: .}}' exit 0
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withmaxdmyers-recall
A self-improving layer for Claude Code: it learns from your sessions, retains knowledge in a vault, and proposes skills/automations from recurring patterns. This repo holds the machinery (hooks, distill prompt + runner, dashboard, install config).
Get the whole plugin

