| name | advanced-statusline |
| description | Implement AI-powered statusline with session tracking, plan detection, workspace emojis, and intelligent caching for Claude Code |
| tags | statusline, hooks, shell, starship, caching |
Advanced Claude Code Statusline System
This skill implements a comprehensive statusline system for Claude Code featuring:
- ๐ค AI-powered session summaries using Claude Haiku
- ๐ Auto-generated session names (filters trivial inputs)
- ๐ Plan-session correlation tracking
- ๐ง Real-time assistant tool usage display
- ๐ฆ Workspace-aware emojis
- โก Starship integration with proper escape sequence handling
- ๐งน Intelligent cache management (100MB threshold, 7-day retention)
- ๐ค Idle-friendly (keeps cached values indefinitely until new user action)
Architecture
Components
Main Statusline Wrapper (
statusline-starship-wrapper.sh)- Receives JSON input from Claude Code
- Integrates with Starship for git/directory info
- Generates 3-line output with workspace emoji, summary, session info
- Reads from hook-generated caches
Session Naming Hook (
session-naming.sh)- Trigger:
UserPromptSubmit - Filters trivial inputs (yes, no, continue, < 10 chars)
- Immediate fallback write before AI call (prevents "Unnamed Session" race condition)
- Generates concise session names via Claude Haiku (5-second timeout)
- Regenerates when new messages arrive
- Uses word-boundary truncation for readable fallbacks
- Trigger:
Plan Tracking Hook (
plan-tracking.sh)- Triggers:
SessionStart,UserPromptSubmit - Detects plans created within 10 minutes
- Maintains session-plan correlation map
- Shows ๐ indicator when plan is active
- Triggers:
Assistant Output Sampling Hook (
assistant-output-sampling.sh)- Trigger:
PostToolUse(after every tool execution) - Records tool usage patterns
- Shows top 3 tools as "๐ง Edit, Write, Bash"
- Updates on every action
- Trigger:
Shared Cache Cleanup (
cache-cleanup.sh)- Runs randomly (1% chance) from all components
- Only activates when total cache > 100MB
- Deletes files older than 7 days
- Protects recent activity
Data Flow
User Input โ UserPromptSubmit Hook โ session-naming.sh โ /tmp/claude-session-name-{session_id}.txt
โ plan-tracking.sh โ /tmp/claude-session-plan-{session_id}.txt
Tool Execution โ PostToolUse Hook โ assistant-output-sampling.sh โ /tmp/claude-assistant-summary-{session_id}.txt
Statusline Refresh โ statusline-starship-wrapper.sh โ Reads all caches โ Displays 3-line output
Statusline Output Format
๐ฆ Sonnet 4.5 | [starship git status] | 63% ctx
Implementing advanced statusline ๐ง Edit, Write, Bash | abc-123-session-id
Advanced Statusline System ๐ plan-name-if-active
Implementation
Step 1: Create Hook Scripts
File: ~/.claude/hooks/session-naming.sh
#!/bin/bash
set -euo pipefail
# Read hook input
input=$(cat)
session_id=$(echo "$input" | jq -r '.session_id // ""')
user_prompt=$(echo "$input" | jq -r '.user_prompt // ""')
# Exit if no session ID or prompt
if [ -z "$session_id" ] || [ -z "$user_prompt" ]; then
exit 0
fi
# Filter out trivial inputs
if echo "$user_prompt" | grep -qiE '^(yes|no|ok|okay|sure|continue|try again|test it|fix it|run it|check|show|nope|yep|go ahead|proceed)\.?$'; then
exit 0
fi
# Also filter out very short inputs (< 10 chars)
if [ ${#user_prompt} -lt 10 ]; then
exit 0
fi
# Cache files
cache_file="/tmp/claude-session-name-${session_id}.txt"
timestamp_file="/tmp/claude-session-name-${session_id}.timestamp"
msg_count_file="/tmp/claude-session-name-${session_id}.msgcount"
# Check if we need to regenerate (similar to summary logic)
should_regenerate=false
history_file="$HOME/.claude/history.jsonl"
# Get current message count
current_msg_count=0
if [ -f "$history_file" ]; then
current_msg_count=$(grep -c "\"sessionId\":\"${session_id}\"" "$history_file" 2>/dev/null || echo "0")
current_msg_count=$(echo "$current_msg_count" | tr -d '\n\r ')
fi
current_msg_count=${current_msg_count:-0}
if [ ! -f "$cache_file" ]; then
# No cache exists, generate new name
should_regenerate=true
else
cached_name=$(cat "$cache_file" 2>/dev/null || echo "")
# If cache is empty or invalid, regenerate
if [ -z "$cached_name" ] || [ ${#cached_name} -lt 5 ]; then
should_regenerate=true
else
# Check if new messages exist since last generation
last_msg_count=$(cat "$msg_count_file" 2>/dev/null | tr -d '\n\r ' || echo "0")
last_msg_count=${last_msg_count:-0}
if [ "$current_msg_count" -gt "$last_msg_count" ]; then
should_regenerate=true
fi
fi
fi
# If we don't need to regenerate, exit early
if [ "$should_regenerate" = false ]; then
exit 0
fi
# IMMEDIATELY write a fallback name to prevent "Unnamed Session" race condition
# This ensures cache file exists even if AI call times out or fails
# The AI call below will overwrite this with a better name if successful
fallback_name=""
for word in $user_prompt; do
if [ -z "$fallback_name" ]; then
# First word - capitalize it
fallback_name="$(echo "${word:0:1}" | tr '[:lower:]' '[:upper:]')${word:1}"
else
# Check if adding this word would exceed limit
test_name="$fallback_name $word"
if [ ${#test_name} -le 37 ]; then
fallback_name="$test_name"
else
# Would exceed limit, add ellipsis and break
fallback_name="${fallback_name}..."
break
fi
fi
done
# If we got through all words without truncating, don't add ellipsis
if [[ ! "$fallback_name" =~ \.\.\.$ ]]; then
# Ensure it's not too long anyway
if [ ${#fallback_name} -gt 40 ]; then
fallback_name="${fallback_name:0:37}..."
fi
fi
# Write the fallback name immediately to cache
echo "$fallback_name" > "$cache_file"
date +%s > "$timestamp_file"
echo "$current_msg_count" > "$msg_count_file"
# Generate session name using AI (with timeout)
schema='{"type":"object","properties":{"name":{"type":"string","maxLength":40}},"required":["name"]}'
prompt="Generate a concise session name (3-5 words, max 40 chars) for this task: ${user_prompt}"
ai_temp="/tmp/claude-session-name-ai-${session_id}-$$.json"
# Run with timeout (5 seconds - matches settings.json timeout)
(
echo "$prompt" | claude --model haiku -p --no-session-persistence --output-format json --json-schema "$schema" 2>/dev/null > "$ai_temp"
) &
claude_pid=$!
for i in {1..5}; do
if ! kill -0 $claude_pid 2>/dev/null; then
break
fi
sleep 1
done
kill $claude_pid >/dev/null 2>&1 || true
wait $claude_pid >/dev/null 2>&1 || true
# Extract name from AI response
ai_session_name=""
if [ -f "$ai_temp" ] && [ -s "$ai_temp" ]; then
ai_session_name=$(jq -r '.structured_output.name // empty' "$ai_temp" 2>/dev/null || echo "")
rm -f "$ai_temp"
fi
# If AI succeeded with a valid name, overwrite the fallback cache
if [ -n "$ai_session_name" ] && [ ${#ai_session_name} -ge 5 ]; then
echo "$ai_session_name" > "$cache_file"
date +%s > "$timestamp_file"
echo "$current_msg_count" > "$msg_count_file"
fi
# Otherwise, keep the immediate fallback that was already written
# Occasionally run shared cleanup (1% of the time to minimize overhead)
if [ $((RANDOM % 100)) -eq 0 ]; then
bash "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/cache-cleanup.sh" &
fi
exit 0
File: ~/.claude/hooks/plan-tracking.sh
#!/bin/bash
set -euo pipefail
# Read hook input
input=$(cat)
session_id=$(echo "$input" | jq -r '.session_id // ""')
hook_event=$(echo "$input" | jq -r '.hook_event_name // ""')
if [ -z "$session_id" ]; then
exit 0
fi
# Plan tracking file
plan_map="/tmp/claude-plan-session-map.jsonl"
# On SessionStart or UserPromptSubmit, check for recent plans
if [ "$hook_event" = "SessionStart" ] || [ "$hook_event" = "UserPromptSubmit" ]; then
# Find most recent plan (within last 10 minutes)
recent_plan=$(find ~/.claude/plans -name "*.md" -mmin -10 -type f 2>/dev/null | head -1)
if [ -n "$recent_plan" ]; then
plan_name=$(basename "$recent_plan" .md)
# Record mapping
echo "{\"session_id\":\"${session_id}\",\"plan\":\"${plan_name}\",\"timestamp\":$(date +%s)}" >> "$plan_map"
# Cache current session's plan
echo "$plan_name" > "/tmp/claude-session-plan-${session_id}.txt"
fi
fi
# Keep plan map under control (keep only last 1000 entries)
if [ -f "$plan_map" ]; then
tail -1000 "$plan_map" > "${plan_map}.tmp" && mv "${plan_map}.tmp" "$plan_map"
fi
# Occasionally run shared cleanup (1% of the time)
if [ $((RANDOM % 100)) -eq 0 ]; then
bash ~/.claude/hooks/cache-cleanup.sh &
fi
exit 0
File: ~/.claude/hooks/assistant-output-sampling.sh
#!/bin/bash
set -euo pipefail
# Read hook input
input=$(cat)
session_id=$(echo "$input" | jq -r '.session_id // ""')
hook_event=$(echo "$input" | jq -r '.hook_event_name // ""')
if [ -z "$session_id" ]; then
exit 0
fi
# Only run on PostToolUse
if [ "$hook_event" != "PostToolUse" ]; then
exit 0
fi
# Extract tool information
tool_name=$(echo "$input" | jq -r '.tool_name // ""')
tool_input=$(echo "$input" | jq -r '.tool_input // {}')
# Skip empty or system tools
if [ -z "$tool_name" ] || [ "$tool_name" = "null" ]; then
exit 0
fi
# Cache file for assistant actions
actions_cache="/tmp/claude-assistant-actions-${session_id}.jsonl"
# Record action with timestamp
action_entry=$(jq -n \
--arg tool "$tool_name" \
--arg timestamp "$(date +%s)" \
--argjson input "$tool_input" \
'{tool: $tool, timestamp: $timestamp, input: $input}')
echo "$action_entry" >> "$actions_cache"
# Keep only last 50 actions
if [ -f "$actions_cache" ]; then
tail -50 "$actions_cache" > "${actions_cache}.tmp" && mv "${actions_cache}.tmp" "$actions_cache"
fi
# Generate summary of recent actions (last 10)
recent_actions=$(tail -10 "$actions_cache" 2>/dev/null | jq -r '.tool' | sort | uniq -c | sort -rn | head -3 | awk '{print $2}' | paste -sd ', ' -)
if [ -n "$recent_actions" ]; then
echo "$recent_actions" > "/tmp/claude-assistant-summary-${session_id}.txt"
fi
# Occasionally run shared cleanup (1% of the time)
if [ $((RANDOM % 100)) -eq 0 ]; then
bash ~/.claude/hooks/cache-cleanup.sh &
fi
exit 0
File: ~/.claude/hooks/cache-cleanup.sh
#!/bin/bash
# Shared cache cleanup utility - only cleans when total cache > 100MB
# Keeps recent files (last 7 days) even when cleaning
set -euo pipefail
# Calculate total cache size
cache_size=$(du -sm /tmp/claude-* 2>/dev/null | awk '{sum+=$1} END {print sum+0}')
# Only cleanup if > 100MB
if [ "$cache_size" -lt 100 ]; then
exit 0
fi
# Cleanup old files (> 7 days) when cache is large
find /tmp -name "claude-session-summary-*.txt" -mtime +7 -delete 2>/dev/null
find /tmp -name "claude-session-summary-*.timestamp" -mtime +7 -delete 2>/dev/null
find /tmp -name "claude-session-summary-*.msgcount" -mtime +7 -delete 2>/dev/null
find /tmp -name "claude-session-name-*.txt" -mtime +7 -delete 2>/dev/null
find /tmp -name "claude-session-name-*.timestamp" -mtime +7 -delete 2>/dev/null
find /tmp -name "claude-session-name-*.msgcount" -mtime +7 -delete 2>/dev/null
find /tmp -name "claude-session-plan-*.txt" -mtime +7 -delete 2>/dev/null
find /tmp -name "claude-assistant-actions-*.jsonl" -mtime +7 -delete 2>/dev/null
find /tmp -name "claude-assistant-summary-*.txt" -mtime +7 -delete 2>/dev/null
# Also cleanup temporary AI files (always clean these up if > 1 hour old)
find /tmp -name "claude-ai-summary-*.json" -mmin +60 -delete 2>/dev/null
find /tmp -name "claude-session-name-ai-*.json" -mmin +60 -delete 2>/dev/null
exit 0
Step 2: Create Main Statusline Script
File: ~/.claude/statusline-starship-wrapper.sh
Click to expand full script (233 lines)
#!/bin/bash
# Read JSON input from stdin
input=$(cat)
# Extract current directory from JSON
cwd=$(echo "$input" | jq -r '.workspace.current_dir')
# Assign workspace emoji based on path
get_workspace_emoji() {
local path="$1"
local basename=$(basename "$path")
# Semantic mapping for common project types
case "$basename" in
*caro*) echo "๐ฆ" ;; # Rust crab
*rust*) echo "๐ฆ" ;;
*node*|*npm*|*react*|*next*) echo "๐ฆ" ;;
*python*|*py*) echo "๐" ;;
*go*|*golang*) echo "๐น" ;;
*java*) echo "โ" ;;
*docker*) echo "๐ณ" ;;
*web*|*site*) echo "๐" ;;
*api*) echo "๐" ;;
*db*|*database*) echo "๐๏ธ" ;;
*docs*|*documentation*) echo "๐" ;;
*test*) echo "๐งช" ;;
*)
# Hash-based consistent emoji for unknown paths
local emojis=("๐ผ" "๐" "๐ ๏ธ" "โ๏ธ" "๐ง" "๐" "๐ฏ" "๐" "๐ก" "๐ฌ")
local hash=$(echo -n "$path" | cksum | cut -d' ' -f1)
local index=$((hash % ${#emojis[@]}))
echo "${emojis[$index]}"
;;
esac
}
workspace_emoji=$(get_workspace_emoji "$cwd")
# Extract model display name
model_name=$(echo "$input" | jq -r '.model.display_name')
# Extract session ID
session_id=$(echo "$input" | jq -r '.session_id')
# Read session name from cache (generated by hook)
session_name_cache="/tmp/claude-session-name-${session_id}.txt"
if [ -f "$session_name_cache" ]; then
session_name=$(cat "$session_name_cache" 2>/dev/null || echo "")
fi
# Fallback if no cached name
if [ -z "$session_name" ]; then
session_name=$(echo "$input" | jq -r '.session_name // "Unnamed Session"')
fi
# Read plan info from cache (generated by hook)
plan_cache="/tmp/claude-session-plan-${session_id}.txt"
plan_indicator=""
if [ -f "$plan_cache" ]; then
plan_name=$(cat "$plan_cache" 2>/dev/null || echo "")
if [ -n "$plan_name" ]; then
plan_indicator=" ๐ $plan_name"
fi
fi
# Read assistant action summary from cache (generated by PostToolUse hook)
assistant_summary_cache="/tmp/claude-assistant-summary-${session_id}.txt"
assistant_actions=""
if [ -f "$assistant_summary_cache" ]; then
actions=$(cat "$assistant_summary_cache" 2>/dev/null || echo "")
if [ -n "$actions" ]; then
assistant_actions=" ๐ง $actions"
fi
fi
# Calculate context window percentage
usage=$(echo "$input" | jq '.context_window.current_usage')
if [ "$usage" != "null" ]; then
current=$(echo "$usage" | jq '.input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens')
size=$(echo "$input" | jq '.context_window.context_window_size')
context_pct=$((current * 100 / size))
context_display=" | ${context_pct}% ctx"
else
context_display=""
fi
# Set up environment variables that Starship expects
export PWD="$cwd"
export STARSHIP_SHELL="bash"
# Change to the directory so git commands work correctly
cd "$cwd" 2>/dev/null || cd "$HOME"
# Run starship with the prompt command and strip the \[\] escape sequences
starship_output=$(starship prompt 2>/dev/null | sed 's/\\\[//g; s/\\\]//g' | tr -d '\n')
# Generate session summary (line 2)
summary_cache="/tmp/claude-session-summary-${session_id}.txt"
summary_timestamp="/tmp/claude-session-summary-${session_id}.timestamp"
summary_msg_count="/tmp/claude-session-summary-${session_id}.msgcount"
# Check if we need to regenerate the summary
# Regenerate ONLY if:
# 1. Cache doesn't exist, OR
# 2. New messages exist since last check
# Idle sessions keep their cached summary indefinitely
should_regenerate=false
history_file="$HOME/.claude/history.jsonl"
# Get current message count
current_msg_count=0
if [ -f "$history_file" ]; then
current_msg_count=$(grep -c "\"sessionId\":\"${session_id}\"" "$history_file" 2>/dev/null || echo "0")
fi
if [ ! -f "$summary_cache" ]; then
should_regenerate=true
else
# Check if new messages exist
last_msg_count=$(cat "$summary_msg_count" 2>/dev/null || echo "0")
if [ "$current_msg_count" -gt "$last_msg_count" ]; then
should_regenerate=true
fi
fi
if [ "$should_regenerate" = true ]; then
# Read from history.jsonl instead of transcript
history_file="$HOME/.claude/history.jsonl"
if [ -f "$history_file" ]; then
# Process line-by-line to avoid jq -s parse errors
first_user_msg=$(grep "\"sessionId\":\"${session_id}\"" "$history_file" | head -1 | jq -r '.display // ""')
recent_user_msgs=$(grep "\"sessionId\":\"${session_id}\"" "$history_file" | tail -7 | jq -r '.display' | paste -sd ' ' -)
user_msg_count=$(grep -c "\"sessionId\":\"${session_id}\"" "$history_file")
else
first_user_msg=""
recent_user_msgs=""
user_msg_count=0
fi
# PRIMARY METHOD: AI summary with structured output
# Use ALL user messages (no keyword filtering - let AI determine what's meaningful)
all_user_msgs=$(grep "\"sessionId\":\"${session_id}\"" "$history_file" | jq -r '.display')
# Get first + last 5 user messages for context
first_user_msgs=$(echo "$all_user_msgs" | head -5 | paste -sd '. ' -)
last_user_msgs=$(echo "$all_user_msgs" | tail -5 | paste -sd '. ' -)
# Use structured JSON output with examples
schema='{"type":"object","properties":{"task":{"type":"string","maxLength":60}},"required":["task"]}'
prompt="Summarize this development session in max 60 chars based on what the user asked for and worked on.
User's requests (first 5): $first_user_msgs
User's requests (last 5): $last_user_msgs
Examples: Configuring statusline, Adding AI summaries, Fixing build errors, Refactoring auth module"
# Call Claude with structured output using temp file for timeout handling
ai_temp="/tmp/claude-ai-summary-${session_id}-$$.json"
(
echo "$prompt" | claude --model haiku -p --no-session-persistence --output-format json --json-schema "$schema" 2>/dev/null > "$ai_temp"
) &
claude_pid=$!
# Wait up to 3 seconds for completion (statusline needs to be fast)
for i in {1..3}; do
if ! kill -0 $claude_pid 2>/dev/null; then
break
fi
sleep 1
done
# Kill if still running (suppress all output)
kill $claude_pid >/dev/null 2>&1
wait $claude_pid >/dev/null 2>&1
# Extract summary from temp file
ai_summary=""
if [ -f "$ai_temp" ] && [ -s "$ai_temp" ]; then
ai_summary=$(jq -r '.structured_output.task // empty' "$ai_temp" 2>/dev/null)
rm -f "$ai_temp"
fi
# Check if AI summary worked and is meaningful
if [ -n "$ai_summary" ] && [ ${#ai_summary} -gt 10 ] && ! echo "$ai_summary" | grep -qiE "^(Ready|Awaiting|Clarify|Assist)"; then
summary="$ai_summary"
else
# FALLBACK: Use first + last message
first_line=$(echo "$first_user_msg" | head -n 1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | cut -c 1-47)
last_user_msg=$(grep "\"sessionId\":\"${session_id}\"" "$history_file" | tail -1 | jq -r '.display // ""' | head -n 1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | cut -c 1-47)
if [ "$user_msg_count" -le 1 ] || [ "$first_line" = "$last_user_msg" ]; then
summary=$(echo "$first_line" | cut -c 1-100)
else
summary="${first_line} โ ${last_user_msg}"
fi
fi
# Clean up summary
summary=$(echo "$summary" | tr -s ' ' | tr -d '\n\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Final fallback if still empty
if [ -z "$summary" ]; then
summary="Active session"
fi
# Cache the summary and message count
echo "$summary" > "$summary_cache"
date +%s > "$summary_timestamp"
echo "$current_msg_count" > "$summary_msg_count"
else
# Use cached summary
summary=$(cat "$summary_cache" 2>/dev/null || echo "Active session")
fi
# Occasionally run shared cleanup (1% of the time to minimize overhead)
if [ $((RANDOM % 100)) -eq 0 ]; then
bash ~/.claude/hooks/cache-cleanup.sh &
fi
# Combine workspace emoji, model name, starship output, and context percentage (line 1)
# Add session summary with session ID and assistant actions (line 2)
# Add session name with plan indicator (line 3)
printf "%s %s | %s%s\n%s%s | %s\n%s%s" "$workspace_emoji" "$model_name" "$starship_output" "$context_display" "$summary" "$assistant_actions" "$session_id" "$session_name" "$plan_indicator"
Step 3: Make Scripts Executable
chmod +x ~/.claude/hooks/*.sh
chmod +x ~/.claude/statusline-starship-wrapper.sh
Step 4: Configure Settings
Add to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline-starship-wrapper.sh"
},
"hooks": {
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/session-naming.sh",
"timeout": 5
},
{
"type": "command",
"command": "bash ~/.claude/hooks/plan-tracking.sh",
"timeout": 2
}
]
}
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/assistant-output-sampling.sh",
"timeout": 1
}
]
}
],
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/plan-tracking.sh",
"timeout": 2
}
]
}
]
}
}
Claude Code Internals
JSON Input Format
Claude Code passes JSON to the statusline command via stdin:
{
"session_id": "abc-123-def",
"session_name": "My Session",
"workspace": {
"current_dir": "/path/to/project"
},
"model": {
"display_name": "Sonnet 4.5"
},
"context_window": {
"current_usage": {
"input_tokens": 50000,
"output_tokens": 10000,
"cache_creation_input_tokens": 5000,
"cache_read_input_tokens": 30000
},
"context_window_size": 200000
}
}
Hook Input Format
Hooks receive different JSON based on event type:
UserPromptSubmit:
{
"session_id": "abc-123",
"user_prompt": "The text the user typed",
"hook_event_name": "UserPromptSubmit",
...
}
PostToolUse:
{
"session_id": "abc-123",
"tool_name": "Edit",
"tool_input": {"file_path": "/path", ...},
"tool_result": {...},
"hook_event_name": "PostToolUse",
...
}
File Locations
- History:
~/.claude/history.jsonl- User messages only - Transcripts:
~/.claude/projects/-{project-path}/{session-id}.jsonl - Plans:
~/.claude/plans/{plan-name}.md - Settings:
~/.claude/settings.json
Starship Integration
Escape Sequence Handling
Starship outputs bash readline escapes (\[ and \]) that must be stripped:
starship_output=$(starship prompt 2>/dev/null | sed 's/\\\[//g; s/\\\]//g' | tr -d '\n')
Why: Statusline doesn't use readline, these escapes cause display issues.
Environment Setup
Starship requires these environment variables:
export PWD="$cwd"
export STARSHIP_SHELL="bash"
cd "$cwd" 2>/dev/null || cd "$HOME"
Why: Starship uses PWD for directory display and git commands need correct working directory.
Cache Management Strategy
Design Principles
- Idle-Friendly: Never regenerate if no user activity
- Threshold-Based: Only cleanup when cache > 100MB
- Recency Protection: Keep files < 7 days always
- Low Overhead: 1% random execution, background processing
Cache Files
| File Pattern | Purpose | Lifetime |
|---|---|---|
claude-session-name-*.txt |
Auto-generated session names | Until 100MB + 7 days |
claude-session-name-*.timestamp |
Name generation timestamps | Until 100MB + 7 days |
claude-session-name-*.msgcount |
Message count tracking | Until 100MB + 7 days |
claude-session-summary-*.txt |
AI-generated summaries | Until 100MB + 7 days |
claude-session-summary-*.timestamp |
Summary generation timestamps | Until 100MB + 7 days |
claude-session-summary-*.msgcount |
Summary message count tracking | Until 100MB + 7 days |
claude-session-plan-*.txt |
Active plan detection | Until 100MB + 7 days |
claude-assistant-summary-*.txt |
Tool usage summaries | Until 100MB + 7 days |
claude-assistant-actions-*.jsonl |
Full action logs | Until 100MB + 7 days |
claude-ai-summary-*.json |
Temp AI responses | 1 hour (always) |
claude-session-name-ai-*.json |
Temp session naming AI responses | 1 hour (always) |
claude-plan-session-map.jsonl |
Plan correlation history | Kept at 1000 entries |
Regeneration Logic
Session Name:
- Immediate fallback write (prevents race condition)
- AI upgrade attempt with 5-second timeout
- Regenerates when message count increases
- Readable word-boundary truncation fallbacks
AI Summary:
- Regenerate ONLY when new user messages exist
- Idle sessions keep last summary indefinitely
Plan Detection:
- Check on SessionStart and new user messages
- Detects plans created within 10 minutes
Assistant Actions:
- Update after every tool execution
- Shows top 3 tools from last 10 actions
Troubleshooting
Statusline Not Showing
- Check script permissions:
chmod +x ~/.claude/statusline-starship-wrapper.sh - Test manually:
echo '{"session_id":"test",...}' | bash ~/.claude/statusline-starship-wrapper.sh - Check Starship installed:
which starship - Verify jq installed:
which jq
AI Summaries Not Generating
- Check Claude CLI:
which claude - Test AI call:
echo "test" | claude --model haiku -p --no-session-persistence - Check timeout (3 seconds might be tight for first call)
- Verify cache files exist:
ls -lh /tmp/claude-session-summary-*
Hooks Not Running
- Hooks load at session start - restart Claude Code
- Check hook scripts exist and are executable
- Test hook directly:
echo '{"session_id":"test","user_prompt":"hello"}' | bash ~/.claude/hooks/session-naming.sh - Check settings.json syntax
Cache Growing Too Large
- Check total cache:
du -sm /tmp/claude-* 2>/dev/null | awk '{sum+=$1} END {print sum " MB"}' - Manually trigger cleanup:
bash ~/.claude/hooks/cache-cleanup.sh - Reduce retention (edit cache-cleanup.sh, change
-mtime +7to-mtime +3)
Escape Sequences Visible
If you see \[ or \] in statusline:
- Check sed command in statusline script
- Verify pattern:
's/\\\[//g; s/\\\]//g'(three backslashes) - Test:
echo "\\[test\\]" | sed 's/\\\[//g; s/\\\]//g'
Customization
Add More Workspace Emojis
Edit get_workspace_emoji() function in statusline script:
case "$basename" in
*myproject*) echo "๐ฏ" ;;
*backend*) echo "โ๏ธ" ;;
# Add more...
esac
Change AI Summary Length
Modify schema in statusline script:
schema='{"type":"object","properties":{"task":{"type":"string","maxLength":80}},"required":["task"]}'
Adjust Cache Threshold
Edit cache-cleanup.sh:
# Change 100MB threshold to 50MB
if [ "$cache_size" -lt 50 ]; then
Change Cleanup Age
Edit cache-cleanup.sh:
# Change 7 days to 3 days
find /tmp -name "claude-session-summary-*.txt" -mtime +3 -delete
Performance Characteristics
- Statusline refresh: ~50-100ms (cached), ~3-5s (AI regeneration)
- Hook execution: 1-5 seconds per hook
- Cache cleanup: Background, non-blocking
- Memory footprint: ~1-2MB per session (caches)
- Disk usage: 60-100MB typical cache size
Security Considerations
- All cache files in
/tmp(world-readable on most systems) - Session IDs visible in file names
- No sensitive data should be in summaries
- AI calls use
--no-session-persistence(no data retention) - Scripts use
set -euo pipefailfor safety
Future Enhancements
- Support for remote sessions (different transcript paths)
- Integration with episodic memory for cross-session context
- Configurable emoji sets via settings
- Multi-line summary support for complex sessions
- Git branch-based session grouping
- Export session summaries to markdown reports