Skip to main content
hooksSource-backedReview first Safety Privacy

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.

by gyanesh-m·added 2026-05-19·
Claude Code
HarnessClaude Code
Trigger:SessionStart
Review first review before installing

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
Source repository stats
Scope
Source repo
Runtime and command metadata
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"
Tool listing metadata
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 -p background worker (the "scout") that researches your weakest metrics on docs.anthropic.com and 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

  1. Install via the Claude Code plugin marketplace (recommended):

    /plugin marketplace add gyanesh-m/retro-daily
    /plugin install retro-daily@retro-daily
    /reload-plugins
    
  2. Or add the SessionStart hook manually to ~/.claude/settings.json using the configuration above, pointing at the startup.sh entry 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/bash works for the dashboard; the scout runner uses nohup so no setsid requirement).
  • Python 3.9+ for the metrics renderer (generate-metrics.py).
  • Optional: an authenticated claude session in your keychain so the background scout can call claude -p without 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:

  1. daily-insights.sh prints the editorial dashboard from the cached metrics-store.json. Regeneration (embedding classification) is gated by last-run-date.txt so it runs at most once per day.
  2. scout.sh spawns scout-runner.sh as a detached nohup background worker if the weak-metric set changed or the last scout is older than SCOUT_STALE_DAYS (default 7). A pgrep guard prevents double-spawn.
  3. scout-runner.sh invokes claude -p --allowedTools "WebSearch Write" with a research prompt over docs.anthropic.com, GitHub, and GitHub Trending, writing results to /tmp/ first and atomically mv-ing them into place. Previous results are archived.
  4. scout-review.sh renders 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.

#sessionstart#dashboard#metrics#retro#analytics

Source citations

Signals

Loading live community signals…

More like this, weekly

A short, calm digest of reviewed Claude resources. Unsubscribe any time.