Retro Daily - Hooks
SessionStart hook that prints a daily Claude Code retro at the top of every session — competency grade (0–100, A–F), 14-day efficiency sparklines, and a year-long contributions heatmap.
Open the source and read safety notes before installing.
Safety notes
- Runs automatically on Claude Code SessionStart and can spawn detached background claude -p workers.
- Uses WebSearch and Write in the scout worker unless RETRO_DAILY_NO_BACKGROUND_WORKERS=1 is set.
- Writes temporary scout and tagger output to /tmp before moving validated results into ~/.claude/metrics.
- First full setup can install large Python model dependencies for local classification.
- Scout findings may include web- or GitHub-derived text and should be treated as untrusted informational context, not executable instructions.
Privacy notes
- Reads local Claude Code session history from ~/.claude/projects/*.jsonl to compute metrics.
- Writes derived metrics, logs, scout results, and session tags under ~/.claude/metrics.
- Session tagger builds a truncated digest from recent local session messages for claude -p labeling.
- Background scout may send weak-metric research queries to docs.anthropic.com and GitHub through WebSearch.
- The SessionStart hook may provide the rendered retro/scout dashboard to Claude Code as hookSpecificOutput.additionalContext, which is model-visible context in addition to the user-visible dashboard.
Prerequisites
- Claude Code CLI installed and authenticated.
- Python 3.9+ for metrics rendering.
- Reads local Claude Code session logs from ~/.claude/projects.
- Writes derived metrics, logs, scout results, and session tags under ~/.claude/metrics.
- Starts sandboxed background claude -p workers unless RETRO_DAILY_NO_BACKGROUND_WORKERS=1 is set.
Schema details
- Install type
- cli
- Reading time
- 2 min
- Difficulty score
- 1
- Troubleshooting
- Yes
- Breaking changes
- No
- Scope
- Source repo
- Trigger
- SessionStart
- Script language
- bash
Script body
#!/bin/bash
# startup.sh — Single SessionStart hook that runs all startup scripts sequentially.
# Each step runs independently (one failure does not skip the rest). Per-step
# output + timing mirrored to a private ~/.claude/metrics/startup.log for
# debugging what Claude Code's hook pipeline actually receives.
# NOTE: no `set -e` — we want every step to execute even if an earlier one errors.
#
# ─── How SessionStart hook output reaches the user vs. the LLM ───────────────
#
# Per Claude Code's hook spec (https://code.claude.com/docs/en/hooks):
#
# SessionStart hook fires
# │
# ┌───────┴────────┐
# ▼ ▼
# Plain stdout JSON output
# │ │
# ▼ ┌───────┴───────┐
# Claude's ▼ ▼
# context systemMessage additionalContext
# ONLY (VISIBLE to (Claude's context
# (hidden user in only — invisible)
# from terminal)
# user)
#
# In Claude Code v2.1.x's full-screen TUI, plain stdout from a plugin's
# SessionStart hook is captured into the LLM's `additionalContext` and is NOT
# rendered visibly. To make the dashboard appear at the top of the user's
# session, we emit a JSON document with `systemMessage` set to the rendered
# output. Do NOT mirror helper output into `additionalContext`: the dashboard
# can contain local-history summaries and web/GitHub-derived scout findings,
# so it must remain display-only and not become model instructions/context.
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_paths.sh"
DEBUG_LOG_DIR="${RETRO_DAILY_HOME:-$HOME/.claude/metrics}"
previous_umask=$(umask)
umask 077
mkdir -p "$DEBUG_LOG_DIR" 2>/dev/null || true
DEBUG_LOG="$DEBUG_LOG_DIR/startup.log"
touch "$DEBUG_LOG" 2>/dev/null || DEBUG_LOG="/dev/null"
umask "$previous_umask"
if [ "$DEBUG_LOG" != "/dev/null" ]; then
chmod 700 "$DEBUG_LOG_DIR" 2>/dev/null || true
chmod 600 "$DEBUG_LOG" 2>/dev/null || true
fi
{
echo "===== startup.sh invoked at $(date -u +%Y-%m-%dT%H:%M:%SZ) ====="
echo "TERM=$TERM PWD=$PWD USER=${USER:-?}"
echo "CLAUDE_CODE_SESSION=${CLAUDE_CODE_SESSION:-unset}"
echo "PATH=$PATH"
} >> "$DEBUG_LOG" 2>&1
# Accumulate every step's stdout into a single display buffer. At the end we
# emit the concatenated output only as systemMessage for user visibility. We
# intentionally keep raw helper output out of additionalContext because scout
# findings and local-history-derived summaries are untrusted.
ACCUMULATED=""
run_step() {
local name="$1"; shift
local t0=$(date +%s)
local out rc
out=$("$@" 2>&1); rc=$?
local t1=$(date +%s)
{
echo "[$name] exit=$rc bytes=${#out} seconds=$((t1-t0))"
echo "----- begin [$name] stdout -----"
printf '%s\n' "$out"
echo "----- end [$name] stdout -----"
} >> "$DEBUG_LOG"
# Append to the display-only buffer that will end up in systemMessage.
ACCUMULATED="${ACCUMULATED}${out}"$'\n'
}
# Order matters: dashboard first (scrolls to top of terminal), scout findings
# LAST so they stay visible near the prompt when the session opens. Earlier
# reverse order buried scout above the viewport.
run_step "daily-insights" bash "$RETRO_DAILY_HOME/daily-insights.sh" || true
run_step "scout" bash "$RETRO_DAILY_HOME/scout.sh" || true
run_step "tag-sessions" bash "$RETRO_DAILY_HOME/tag-sessions.sh" || true
run_step "scout-review" bash "$RETRO_DAILY_HOME/scout-review.sh" || true
# Emit the SessionStart hook JSON response. Reads ACCUMULATED from stdin so we
# don't have to worry about shell quoting of multi-line ANSI-colored content.
printf '%s' "$ACCUMULATED" | python3 -c '
import json, sys
content = sys.stdin.read().rstrip("\n")
# Keep helper output display-only. Do not pass local-history-derived summaries
# or web/GitHub-derived scout text into additionalContext, where it could be
# interpreted as model instructions in future sessions.
print(json.dumps({
"systemMessage": content,
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Retro Daily rendered a display-only startup dashboard. Raw dashboard, scout, and session-history-derived text was intentionally not added to model context.",
},
}))
' 2>>"$DEBUG_LOG"
echo "===== startup.sh finished at $(date -u +%Y-%m-%dT%H:%M:%SZ) =====" >> "$DEBUG_LOG"Full copyable content
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/metrics/startup.sh"
}
]
}
]
}
}About this resource
Features
- SessionStart hook prints a daily retro dashboard at the top of every Claude Code session, with zero added latency on warm cache days.
- Composite competency score (0–100) and letter grade (A–F) derived from per-metric breakdowns, so you see your trajectory in one glance.
- 14-day efficiency grid: sparklines per metric, 7d/30d delta arrows, and severity dots that highlight regressions before they pile up.
- Year-long contributions heatmap rendered inline as ANSI blocks, GitHub-style, so you can spot streaks and dry spells across sessions.
- Detached, sandboxed
claude -pbackground worker (the "scout") that researches your weakest metrics ondocs.anthropic.comand GitHub between sessions. - Findings surface inline in the next session's dashboard with one line per query: top doc pick plus a trending repo for creative angles.
- Daily cache gate: expensive embedding-based classification runs at most once per day; subsequent sessions reuse the cached store.
- Archives previous scout results to
~/.claude/metrics/scout-archive/and ships an interactive viewer for browsing historical findings.
Use Cases
- Spot regressions early: catch a drop in test-pass rate, build-time, or context-efficiency within a day instead of weeks later.
- Stay calibrated against your own baseline rather than someone else's productivity ideal — every metric is computed from your own session history.
- Discover relevant docs and repos without context-switching: the scout pre-fetches material for whichever metric is currently weakest.
- Maintain a session journal automatically: the contributions heatmap doubles as a long-term activity log across machines and projects.
- Make weekly retros trivial: the dashboard is already there at the top of each session, no separate tool to open.
Installation
Install via the Claude Code plugin marketplace (recommended):
/plugin marketplace add gyanesh-m/retro-daily /plugin install retro-daily@retro-daily /reload-pluginsOr add the SessionStart hook manually to
~/.claude/settings.jsonusing the configuration above, pointing at thestartup.shentry point you place in~/.claude/metrics/.
Config paths
- User settings (global):
~/.claude/settings.json - Project-wide (committed):
.claude/settings.json - Local (not committed):
.claude/settings.local.json
Requirements
- Claude Code CLI installed and authenticated.
- Bash 4+ shell (macOS users: the bundled
/bin/bashworks for the dashboard; the scout runner usesnohupso nosetsidrequirement). - Python 3.9+ for the metrics renderer (
generate-metrics.py). - Optional: an authenticated
claudesession in your keychain so the background scout can callclaude -pwithout prompting.
Security and privacy disclosure
Retro Daily reads local Claude Code session history from ~/.claude/projects/*.jsonl to compute metrics. It writes derived state, logs, scout results, and session tags under ~/.claude/metrics. The startup debug log is created at ~/.claude/metrics/startup.log with user-only file permissions when possible. Startup output is rendered as a display-only dashboard and raw dashboard, scout, and session-history-derived text is intentionally not mirrored into Claude Code additionalContext.
By default, scout.sh and tag-sessions.sh can start detached, sandboxed claude -p background workers. The scout worker uses WebSearch and Write to research weak metrics, writes temporary results to /tmp, validates JSON, then moves the results into ~/.claude/metrics. The session tagger builds a truncated digest from recent local session messages and asks claude -p to label those sessions; it denies network access and writes temporary results to /tmp before moving them into ~/.claude/metrics.
Set RETRO_DAILY_NO_BACKGROUND_WORKERS=1 to disable the background scout and session tagger while keeping the dashboard rendering.
Hook Configuration
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/metrics/startup.sh"
}
]
}
]
}
}
How It Works
On every session start, three scripts run in sequence:
daily-insights.shprints the editorial dashboard from the cachedmetrics-store.json. Regeneration (embedding classification) is gated bylast-run-date.txtso it runs at most once per day.scout.shspawnsscout-runner.shas a detachednohupbackground worker if the weak-metric set changed or the last scout is older thanSCOUT_STALE_DAYS(default 7). Apgrepguard prevents double-spawn.scout-runner.shinvokesclaude -p --allowedTools "WebSearch Write"with a research prompt overdocs.anthropic.com, GitHub, and GitHub Trending, writing results to/tmp/first and atomicallymv-ing them into place. Previous results are archived.scout-review.shrenders a compact SCOUT FINDINGS block on the next session with one line per query.
Troubleshooting
Dashboard not rendering on session start
Confirm the SessionStart hook is registered in ~/.claude/settings.json and that ~/.claude/metrics/startup.sh is executable (chmod +x). Test manually: bash ~/.claude/metrics/startup.sh. Force a regenerate by removing the daily cache: rm ~/.claude/metrics/last-run-date.txt && python3 ~/.claude/metrics/generate-metrics.py.
Scout worker dies immediately on macOS
macOS has no setsid, so nohup alone is not enough to survive the parent Claude Code session closing. The runner traps SIGHUP at the top (trap '' HUP) and scout.sh redirects stdin from /dev/null to keep the worker alive after the session exits.
claude -p --bare returns auth errors from the scout
--bare disables OAuth and keychain auth. Drop --bare so the user's session auth applies. The runner uses --allowedTools "WebSearch Write" for the right scope.
Scout cannot write to ~/.claude/
The Claude Code sensitive-path guard blocks headless claude -p writes inside ~/.claude/ even with --allowedTools. The runner writes to /tmp/claude-scout-results-$$.json first, validates the JSON, then mvs it into place.
SessionStart hook denied for autonomous-agent safety check
SessionStart hooks are exempt from the per-command autonomous-agent safety check, so the runner spawns fine from scout.sh. Re-running the runner manually from inside a session will be denied; test it via bash ~/.claude/metrics/scout-runner.sh from an external terminal.
- Features
- Use Cases
- Installation
- Config paths
- Requirements
- Security and privacy disclosure
- Hook Configuration
- How It Works
- Troubleshooting
- Dashboard not rendering on session start
- Scout worker dies immediately on macOS
- `claude -p --bare` returns auth errors from the scout
- Scout cannot write to `~/.claude/`
- SessionStart hook denied for autonomous-agent safety check
Source citations
Signals
Loading live community signals…
A short, calm digest of reviewed Claude resources. Unsubscribe any time.