Skip to content

Instantly share code, notes, and snippets.

@jakkaj
Last active June 24, 2025 21:14
Show Gist options
  • Save jakkaj/0db756404938dafc3a7bc9fb5059247b to your computer and use it in GitHub Desktop.
Save jakkaj/0db756404938dafc3a7bc9fb5059247b to your computer and use it in GitHub Desktop.
Claude Session Search Tools - Bash scripts for searching and listing Claude Code session files

Claude Session Search Tools

Two bash scripts for searching and listing Claude Code session files.

Scripts

1. search-claude-sessions.sh

Search through Claude session content using regex patterns.

Basic Usage:

./search-claude-sessions.sh 'pattern'

Examples:

# Search for query system mentions
./search-claude-sessions.sh 'query.*system'

# Search with context (2 lines before/after)
./search-claude-sessions.sh -c 'error|exception'

# Search with more context lines
./search-claude-sessions.sh -c -n 5 'TODO|FIXME'

# List only files containing pattern (don't show matches)
./search-claude-sessions.sh -l 'import.*pandas'

# Search all projects
./search-claude-sessions.sh -a 'docker'

2. list-claude-sessions.sh

List recent Claude sessions with summaries and statistics.

Basic Usage:

./list-claude-sessions.sh

Examples:

# List 5 most recent sessions
./list-claude-sessions.sh -n 5

# List sessions sorted by size
./list-claude-sessions.sh -s size

# List sessions from all projects
./list-claude-sessions.sh -a

# List 20 sessions from all projects
./list-claude-sessions.sh -a -n 20

Session File Location

  • Sessions are stored in: /home/vscode/.claude/projects/
  • Each project has its own subdirectory
  • Current project: -workspaces-substrate
  • Files are in JSONL format (one JSON object per line)

Tips

  1. Use quotes around regex patterns with special characters
  2. The search is case-sensitive by default
  3. Use | for OR patterns: 'error|warning|exception'
  4. Use .* for wildcards: 'query.*system'
  5. Both scripts support color output for better readability
#!/bin/bash
# List Claude Sessions Script
# Lists Claude sessions with their summaries and timestamps
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Default values
CLAUDE_DIR="/home/vscode/.claude/projects"
PROJECT_PATH="-workspaces-substrate"
LIMIT=10
SORT_BY="time" # time or size
# Usage function
usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "List Claude session files with summaries"
echo ""
echo "OPTIONS:"
echo " -a, --all List sessions from all projects"
echo " -p, --project PATH Specify project path (default: -workspaces-substrate)"
echo " -n, --number NUM Number of sessions to show (default: 10)"
echo " -s, --sort TYPE Sort by: time (default) or size"
echo " -h, --help Show this help message"
}
# Parse arguments
SEARCH_ALL_PROJECTS=false
while [[ $# -gt 0 ]]; do
case $1 in
-a|--all)
SEARCH_ALL_PROJECTS=true
shift
;;
-p|--project)
PROJECT_PATH="$2"
shift 2
;;
-n|--number)
LIMIT="$2"
shift 2
;;
-s|--sort)
SORT_BY="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
usage
exit 1
;;
esac
done
# Function to format file size
format_size() {
local size=$1
if [[ $size -gt 1048576 ]]; then
echo "$(( size / 1048576 ))MB"
elif [[ $size -gt 1024 ]]; then
echo "$(( size / 1024 ))KB"
else
echo "${size}B"
fi
}
# Function to get session info
get_session_info() {
local file="$1"
local session_id=$(basename "$file" .jsonl)
local file_size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null)
local formatted_size=$(format_size "$file_size")
# Get summary from first line
local summary=$(head -1 "$file" 2>/dev/null | jq -r '.summary // "No summary"' 2>/dev/null || echo "No summary")
# Get timestamps
local first_timestamp=$(grep -m1 '"timestamp"' "$file" | jq -r '.timestamp // "Unknown"' 2>/dev/null || echo "Unknown")
local last_timestamp=$(tac "$file" | grep -m1 '"timestamp"' | jq -r '.timestamp // "Unknown"' 2>/dev/null || echo "Unknown")
# Count messages
local user_messages=$(grep -c '"role":"user"' "$file" 2>/dev/null || echo "0")
local assistant_messages=$(grep -c '"role":"assistant"' "$file" 2>/dev/null || echo "0")
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}Session ID:${NC} $session_id"
echo -e "${GREEN}Summary:${NC} $summary"
echo -e "${CYAN}Size:${NC} $formatted_size"
echo -e "${CYAN}Started:${NC} $first_timestamp"
echo -e "${CYAN}Last activity:${NC} $last_timestamp"
echo -e "${CYAN}Messages:${NC} User: $user_messages, Assistant: $assistant_messages"
}
# Main listing logic
echo -e "${BLUE}Claude Session List${NC}"
echo ""
if [[ "$SEARCH_ALL_PROJECTS" == true ]]; then
# List from all projects
temp_file="/tmp/claude_sessions_$$"
for project_dir in "$CLAUDE_DIR"/*; do
if [[ -d "$project_dir" ]]; then
for session_file in "$project_dir"/*.jsonl; do
if [[ -f "$session_file" ]]; then
if [[ "$SORT_BY" == "size" ]]; then
stat -c"%s %n" "$session_file" 2>/dev/null || stat -f"%z %n" "$session_file" 2>/dev/null
else
stat -c"%Y %n" "$session_file" 2>/dev/null || stat -f"%m %n" "$session_file" 2>/dev/null
fi
fi
done
fi
done | sort -rn | head -"$LIMIT" | cut -d' ' -f2- > "$temp_file"
while IFS= read -r session_file; do
get_session_info "$session_file"
done < "$temp_file"
rm -f "$temp_file"
else
# List from specific project
project_dir="$CLAUDE_DIR/$PROJECT_PATH"
if [[ ! -d "$project_dir" ]]; then
echo -e "${RED}Error: Project directory not found: $project_dir${NC}"
exit 1
fi
echo -e "${GREEN}Project:${NC} $PROJECT_PATH"
echo ""
if [[ "$SORT_BY" == "size" ]]; then
ls -S "$project_dir"/*.jsonl 2>/dev/null | head -"$LIMIT" | while IFS= read -r session_file; do
get_session_info "$session_file"
done
else
ls -t "$project_dir"/*.jsonl 2>/dev/null | head -"$LIMIT" | while IFS= read -r session_file; do
get_session_info "$session_file"
done
fi
fi
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
#!/bin/bash
# Claude Session Search Script
# Searches through Claude session JSONL files for content matching a regex pattern
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
CLAUDE_DIR="/home/vscode/.claude/projects"
SEARCH_ALL_PROJECTS=false
PROJECT_PATH="-workspaces-substrate"
SHOW_CONTEXT=false
CONTEXT_LINES=2
# Usage function
usage() {
echo "Usage: $0 [OPTIONS] PATTERN"
echo ""
echo "Search Claude session files for content matching a regex pattern"
echo ""
echo "OPTIONS:"
echo " -a, --all Search all projects (default: current project only)"
echo " -p, --project PATH Specify project path (default: -workspaces-substrate)"
echo " -c, --context Show context lines around matches"
echo " -n, --lines NUM Number of context lines (default: 2)"
echo " -l, --list-only List only matching session files"
echo " -h, --help Show this help message"
echo ""
echo "EXAMPLES:"
echo " $0 'query.*system' # Search for 'query' followed by 'system'"
echo " $0 -a 'import.*pandas' # Search all projects"
echo " $0 -c -n 5 'error|exception' # Show 5 lines of context"
echo " $0 -l 'TODO|FIXME' # List files with TODOs or FIXMEs"
}
# Parse arguments
LIST_ONLY=false
while [[ $# -gt 0 ]]; do
case $1 in
-a|--all)
SEARCH_ALL_PROJECTS=true
shift
;;
-p|--project)
PROJECT_PATH="$2"
shift 2
;;
-c|--context)
SHOW_CONTEXT=true
shift
;;
-n|--lines)
CONTEXT_LINES="$2"
shift 2
;;
-l|--list-only)
LIST_ONLY=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
PATTERN="$1"
shift
;;
esac
done
# Check if pattern is provided
if [[ -z "$PATTERN" ]]; then
echo -e "${RED}Error: No search pattern provided${NC}"
usage
exit 1
fi
# Function to extract and format session info
extract_session_info() {
local file="$1"
local session_id=$(basename "$file" .jsonl)
local summary=$(head -1 "$file" 2>/dev/null | jq -r '.summary // "No summary"' 2>/dev/null || echo "No summary")
local first_timestamp=$(grep -m1 '"timestamp"' "$file" | jq -r '.timestamp // "Unknown"' 2>/dev/null || echo "Unknown")
# Extract first user message
local first_user_msg=""
while IFS= read -r line; do
if echo "$line" | jq -e '.message.role == "user"' >/dev/null 2>&1; then
first_user_msg=$(echo "$line" | jq -r '.message.content' 2>/dev/null | head -c 200)
if [[ ${#first_user_msg} -eq 200 ]]; then
first_user_msg="${first_user_msg}..."
fi
break
fi
done < "$file"
echo -e "${BLUE}Session: ${YELLOW}$session_id${NC}"
echo -e "${GREEN}Summary:${NC} $summary"
echo -e "${GREEN}Started:${NC} $first_timestamp"
if [[ -n "$first_user_msg" ]]; then
echo -e "${GREEN}First message:${NC} $first_user_msg"
fi
}
# Function to search in a single file
search_file() {
local file="$1"
local matches=0
if [[ "$LIST_ONLY" == true ]]; then
if grep -q "$PATTERN" "$file"; then
extract_session_info "$file"
echo -e "${GREEN}File:${NC} $file"
echo ""
return 0
fi
return 1
fi
# Search for user messages and assistant responses
if [[ "$SHOW_CONTEXT" == true ]]; then
# Extract matches with context
local line_num=0
local found_match=false
while IFS= read -r line; do
((line_num++))
# Check if line contains message content
if echo "$line" | jq -e '.message.content' >/dev/null 2>&1; then
local content=$(echo "$line" | jq -r '.message.content' 2>/dev/null)
local role=$(echo "$line" | jq -r '.message.role // .userType // "unknown"' 2>/dev/null)
if echo "$content" | grep -E "$PATTERN" >/dev/null 2>&1; then
if [[ "$found_match" == false ]]; then
extract_session_info "$file"
echo ""
found_match=true
fi
echo -e "${GREEN}Line $line_num (${role}):${NC}"
# Show context lines before
if [[ $CONTEXT_LINES -gt 0 ]]; then
local start=$((line_num - CONTEXT_LINES))
[[ $start -lt 1 ]] && start=1
sed -n "${start},$((line_num-1))p" "$file" | while IFS= read -r ctx_line; do
local ctx_content=$(echo "$ctx_line" | jq -r '.message.content // .summary // "..."' 2>/dev/null | head -1)
echo -e " ${BLUE}...${NC} ${ctx_content:0:80}"
done
fi
# Highlight the match
echo "$content" | grep -E --color=always "$PATTERN"
# Show context lines after
if [[ $CONTEXT_LINES -gt 0 ]]; then
sed -n "$((line_num+1)),$((line_num+CONTEXT_LINES))p" "$file" | while IFS= read -r ctx_line; do
local ctx_content=$(echo "$ctx_line" | jq -r '.message.content // .summary // "..."' 2>/dev/null | head -1)
echo -e " ${BLUE}...${NC} ${ctx_content:0:80}"
done
fi
echo ""
((matches++))
fi
fi
done < "$file"
else
# Simple search without context
local line_num=0
local found_match=false
while IFS= read -r line; do
((line_num++))
# Try to extract content from various fields
local content=""
local role="unknown"
# Try message.content first
if echo "$line" | jq -e '.message.content' >/dev/null 2>&1; then
content=$(echo "$line" | jq -r '.message.content' 2>/dev/null)
role=$(echo "$line" | jq -r '.message.role // .userType // "unknown"' 2>/dev/null)
# Try message.content array (for structured messages)
elif echo "$line" | jq -e '.message.content[0].text' >/dev/null 2>&1; then
content=$(echo "$line" | jq -r '.message.content[0].text' 2>/dev/null)
role=$(echo "$line" | jq -r '.message.role // .userType // "unknown"' 2>/dev/null)
# Try summary field
elif echo "$line" | jq -e '.summary' >/dev/null 2>&1; then
content=$(echo "$line" | jq -r '.summary' 2>/dev/null)
role="summary"
fi
if [[ -n "$content" ]] && echo "$content" | grep -E "$PATTERN" >/dev/null 2>&1; then
if [[ "$found_match" == false ]]; then
extract_session_info "$file"
echo ""
found_match=true
fi
echo -e "${GREEN}Line $line_num (${role}):${NC}"
echo "$content" | grep -E --color=always "$PATTERN"
echo ""
((matches++))
fi
done < "$file"
fi
return $matches
}
# Main search logic
echo -e "${BLUE}Searching for pattern:${NC} $PATTERN"
echo ""
total_matches=0
total_files=0
if [[ "$SEARCH_ALL_PROJECTS" == true ]]; then
# Search all projects
for project_dir in "$CLAUDE_DIR"/*; do
if [[ -d "$project_dir" ]]; then
echo -e "${YELLOW}Project: $(basename "$project_dir")${NC}"
echo ""
for session_file in "$project_dir"/*.jsonl; do
if [[ -f "$session_file" ]]; then
if search_file "$session_file"; then
((total_files++))
fi
fi
done
fi
done
else
# Search specific project
project_dir="$CLAUDE_DIR/$PROJECT_PATH"
if [[ ! -d "$project_dir" ]]; then
echo -e "${RED}Error: Project directory not found: $project_dir${NC}"
exit 1
fi
for session_file in "$project_dir"/*.jsonl; do
if [[ -f "$session_file" ]]; then
if search_file "$session_file"; then
((total_files++))
fi
fi
done
fi
echo -e "${BLUE}Search complete.${NC}"
if [[ "$LIST_ONLY" == true ]]; then
echo -e "${GREEN}Found matches in $total_files session(s)${NC}"
else
echo -e "${GREEN}Found matches in $total_files session(s)${NC}"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment