Global hooks for logging Claude Code conversation events to markdown files. Tracks prompts, tool usage, and responses across all sessions. Useful for debugging, auditing, and providing conversation context to Claude.
/plugin marketplace add ianphil/my-skills/plugin install all-skills@ian-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
scripts/log-conversation.shscripts/prune-logs.shAutomatically log all Claude Code interactions to structured markdown files using global hooks. Enables conversation replay, debugging, and context sharing between sessions.
Each Claude instance gets its own log file to prevent conflicts when running multiple sessions simultaneously.
The logging script is provided in this skill's scripts/ directory. Copy it to your hooks directory:
# Create hooks directory if it doesn't exist
mkdir -p ~/.claude/hooks
# Copy the script from this skill
cp /path/to/my-skills/skills/conversation-logging/scripts/log-conversation.sh ~/.claude/hooks/
# Make it executable
chmod +x ~/.claude/hooks/log-conversation.sh
Or create it manually at ~/.claude/hooks/log-conversation.sh:
#!/bin/bash
# Global hook to log Claude Code conversation events
# Handles multiple concurrent Claude instances by using unique session IDs
LOG_DIR="$HOME/.claude/conversation-logs"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
DATE=$(date '+%Y-%m-%d')
mkdir -p "$LOG_DIR"
# Read JSON input from stdin
INPUT=$(cat)
# Extract key fields using jq if available
if command -v jq &> /dev/null; then
EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"')
CWD=$(echo "$INPUT" | jq -r '.cwd // "unknown"')
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""')
else
EVENT="unknown"
CWD=$(pwd)
SESSION_ID=""
fi
# Create unique log file per session
# Format: YYYY-MM-DD-session-XXXXX.md
if [ -n "$SESSION_ID" ]; then
# Extract first 8 chars of session ID for readability
SHORT_SESSION="${SESSION_ID:0:8}"
LOG_FILE="$LOG_DIR/${DATE}-session-${SHORT_SESSION}.md"
else
# Fallback: use PID if session_id not available
LOG_FILE="$LOG_DIR/${DATE}-pid-$$.md"
fi
# Initialize log file with header if it doesn't exist
if [ ! -f "$LOG_FILE" ]; then
cat > "$LOG_FILE" <<EOF
# Claude Code Conversation Log
**Date:** $DATE
**Session ID:** ${SESSION_ID:-unknown}
**Started:** $TIMESTAMP
---
EOF
fi
# Log based on event type
case "$EVENT" in
UserPromptSubmit)
cat >> "$LOG_FILE" <<EOF
## [$TIMESTAMP] User Prompt
**Working Directory:** \`$CWD\`
\`\`\`
$PROMPT
\`\`\`
EOF
;;
PostToolUse)
if [ -n "$TOOL" ]; then
echo "### [$TIMESTAMP] Tool: \`$TOOL\`" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
fi
;;
Stop)
echo "### [$TIMESTAMP] Response Complete" >> "$LOG_FILE"
echo "" >> "$LOG_FILE"
;;
esac
exit 0
Then make it executable:
chmod +x ~/.claude/hooks/log-conversation.sh
Add hooks to ~/.claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/log-conversation.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/log-conversation.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/log-conversation.sh"
}
]
}
]
}
}
If you already have other settings in your settings.json, merge the hooks section carefully.
Run any Claude Code command and check the logs:
# List all conversation logs
ls -lh ~/.claude/conversation-logs/
# View the most recent log
cat $(ls -t ~/.claude/conversation-logs/*.md | head -1)
Note: Each Claude session creates a unique log file with format YYYY-MM-DD-session-XXXXX.md to prevent conflicts between concurrent instances.
Most recent session:
cat $(ls -t ~/.claude/conversation-logs/*.md | head -1)
All sessions from today:
ls -1 ~/.claude/conversation-logs/$(date +%Y-%m-%d)-*.md
View specific session:
# List today's sessions to find the one you want
ls ~/.claude/conversation-logs/$(date +%Y-%m-%d)-*.md
# Then view it
cat ~/.claude/conversation-logs/2026-01-05-session-a1b2c3d4.md
Recent logs (last 5 sessions):
ls -lt ~/.claude/conversation-logs/ | head -6
Search across all logs:
grep -r "search term" ~/.claude/conversation-logs/
When you want Claude to understand a previous conversation:
Option 1: Reference most recent session
Read my last conversation log:
@$(ls -t ~/.claude/conversation-logs/*.md | head -1)
What were we working on?
Option 2: Reference specific session
Read my conversation from this morning:
@~/.claude/conversation-logs/2026-01-04-session-a1b2c3d4.md
Continue where we left off.
Option 3: Copy relevant sections
Here's what happened in my last session:
[paste relevant log sections]
Continue from where we left off.
Option 4: Search and reference
# Find the conversation about a topic
grep -l "GitHub Issues" ~/.claude/conversation-logs/*.md
Then reference that file with @ in Claude Code.
When Claude encounters errors or you need to replay a session:
# Find failed tool executions in most recent session
grep -A 5 "Tool: Bash" $(ls -t ~/.claude/conversation-logs/*.md | head -1)
# See what prompts led to errors across all today's sessions
grep -B 10 "error" ~/.claude/conversation-logs/$(date +%Y-%m-%d)-*.md
# Find specific session with errors
grep -l "error" ~/.claude/conversation-logs/*.md
Use conversation logs to pick up where you left off:
# View most recent session
cat $(ls -t ~/.claude/conversation-logs/*.md | head -1)
# View yesterday's sessions
ls ~/.claude/conversation-logs/$(date -d yesterday +%Y-%m-%d)-*.md
# Share with Claude
claude -p "Read @$(ls -t ~/.claude/conversation-logs/*.md | head -1) and summarize what we were working on"
Keep your logs directory clean by removing old logs:
Interactive prune (with confirmation):
# Delete logs older than 14 days (default)
~/.claude/hooks/prune-logs.sh
# Delete logs older than 30 days
~/.claude/hooks/prune-logs.sh 30
# Delete logs older than 7 days
~/.claude/hooks/prune-logs.sh 7
Dry run (preview without deleting):
~/.claude/hooks/prune-logs.sh 14 --dry-run
What it does:
Setup the prune script:
# Copy from skill
cp /path/to/my-skills/skills/conversation-logging/scripts/prune-logs.sh ~/.claude/hooks/
# Make executable
chmod +x ~/.claude/hooks/prune-logs.sh
# Create an alias (optional)
echo "alias prune-claude-logs='~/.claude/hooks/prune-logs.sh'" >> ~/.bashrc
source ~/.bashrc
# Now you can just run:
prune-claude-logs
Automated cleanup with cron:
# Add to crontab to run monthly
crontab -e
# Add this line to delete logs older than 30 days on the 1st of each month
0 0 1 * * $HOME/.claude/hooks/prune-logs.sh 30 <<< "y"
Each log file has a unique name per session: YYYY-MM-DD-session-XXXXXXXX.md
Log contents are organized chronologically with rich context:
# Claude Code Conversation Log
**Date:** 2026-01-05
**Session ID:** a1b2c3d4-5678-90ab-cdef-1234567890ab
**Started:** 2026-01-05 14:23:10
---
## [2026-01-05 14:23:15] User Prompt
**Working Directory:** `/home/user/project`
Implement the login feature
### [2026-01-05 14:23:16] Tool: `Read` - `/home/user/project/src/auth.js`
### [2026-01-05 14:23:17] Tool: `Bash`
```bash
git status
<details><summary>Output</summary>
On branch main
Changes not staged for commit:
modified: src/auth.js
</details>
Write - /home/user/project/src/login.jsimport { authenticate } from './auth.js';
export async function login(username, password) {
ret...
</details>
TodoWrite - 3 tasksI've implemented the login feature with proper authentication. The new login.js file handles user authentication and includes error handling...
</details>...
**File naming:**
- Session-based: `2026-01-05-session-a1b2c3d4.md` (first 8 chars of session ID)
- Fallback (if no session ID): `2026-01-05-pid-12345.md` (process ID)
## Customization
### Change Log Location
Edit the script's `LOG_DIR` variable:
```bash
LOG_DIR="$HOME/my-custom-logs"
Extend the script to log additional fields from the hook input:
# Log model info
MODEL=$(echo "$INPUT" | jq -r '.model // "unknown"')
# Log session ID
SESSION=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
Only log certain tools:
PostToolUse)
# Only log Read, Write, Edit tools
if [[ "$TOOL" =~ ^(Read|Write|Edit)$ ]]; then
echo "### [$TIMESTAMP] Tool: $TOOL" >> "$DATE_FILE"
fi
;;
Replace the logging logic to output JSON:
echo "$INPUT" | jq '.' >> "$LOG_DIR/$(date +%Y-%m-%d).jsonl"
Logs not being created?
ls -l ~/.claude/hooks/log-conversation.shcat ~/.claude/settings.json | jq .Logs are empty or incomplete?
jq for better parsing: sudo apt install jq (Linux) or brew install jq (macOS)ls -ld ~/.claude/conversation-logsWant to disable logging temporarily?
~/.claude/settings.jsonmv ~/.claude/hooks/log-conversation.sh{,.disabled}Important considerations:
Recommendations:
Regular cleanup: Use the prune script to delete old logs
# Interactive deletion of logs older than 14 days
~/.claude/hooks/prune-logs.sh
# Or use find directly
find ~/.claude/conversation-logs -name "*.md" -mtime +30 -delete
Exclude from backups: Add to .gitignore or backup exclusions
echo "conversation-logs/" >> ~/.gitignore_global
Encrypt sensitive logs:
gpg -c ~/.claude/conversation-logs/2026-01-05.md
rm ~/.claude/conversation-logs/2026-01-05.md
To log per-project instead of globally, use project-specific hooks in .claude/settings.json within each project:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'cat >> .claude/conversation.md'"
}
]
}
]
}
}
Add .claude/conversation.md to your .gitignore.
| Task | Command |
|---|---|
| View most recent session | cat $(ls -t ~/.claude/conversation-logs/*.md | head -1) |
| List today's sessions | ls ~/.claude/conversation-logs/$(date +%Y-%m-%d)-*.md |
| List all logs | ls -lh ~/.claude/conversation-logs/ |
| Search logs | grep -r "search term" ~/.claude/conversation-logs/ |
| Prune old logs | ~/.claude/hooks/prune-logs.sh (default: 14 days) |
| Prune with custom days | ~/.claude/hooks/prune-logs.sh 30 |
| Dry run prune | ~/.claude/hooks/prune-logs.sh 14 --dry-run |
| Share with Claude | @~/.claude/conversation-logs/YYYY-MM-DD-session-XXXXX.md |
| Disable logging | Rename hook script to .disabled |