Daily Recipes

Ready-to-use automation scripts for everyday tasks

Every recipe in this chapter is a standalone shell script you can copy, customize, and run today. They all follow the same pattern: gather context with standard Unix tools, pass it to claude -p, and capture structured output. Add them to cron, wire them into CI, or run them by hand.

Morning Code Review

Review overnight changes and generate a structured summary. Run this from cron at 9am or trigger it from a Slack webhook.

morning-review.sh
$

The script pulls the git log since a given date, checks for an empty diff, and sends the summary to Claude for analysis:

#!/bin/bash
# morning-review.sh -- Review all changes since yesterday
set -euo pipefail
SINCE="${1:-yesterday}"
DIFF=$(git log --since="$SINCE" --oneline --stat)
if [ -z "$DIFF" ]; then
echo "No changes since $SINCE"
exit 0
fi
CLAUDECODE="" claude -p "Review these git changes and summarize:
- What changed (grouped by area)
- Any potential issues or risks
- Suggested follow-ups
Changes:
$DIFF" \
--output-format text \
--max-budget-usd 0.50 \
--no-session-persistence \
--permission-mode bypassPermissions

Pass a date argument to customize the window: ./morning-review.sh "3 days ago" or ./morning-review.sh "2026-03-15". Add --tools "" to disable file access and keep it as pure text analysis at lower cost.

Auto-Generated Commit Messages

Generate conventional commit messages from staged changes. The script reads the cached diff, sends it to Claude with --tools "" for pure text analysis, then lets you accept, edit, or reject the result.

commit-msg.sh
$
#!/bin/bash
# commit-msg.sh -- Generate commit message from staged changes
set -euo pipefail
DIFF=$(git diff --cached --stat)
PATCH=$(git diff --cached)
[ -z "$DIFF" ] && { echo "No staged changes."; exit 1; }
MSG=$(CLAUDECODE="" claude -p "Generate a conventional commit message (type(scope): description format) for:
Stat: $DIFF
Patch: $PATCH
Keep first line under 72 chars. Add bullet-point body if needed." \
--output-format text --max-budget-usd 0.15 \
--no-session-persistence --permission-mode bypassPermissions --tools "")
echo "$MSG"
read -p "Use this message? (yes/edit/no): " DECISION
case "$DECISION" in
yes) git commit -m "$MSG" ;;
edit) git commit -e -m "$MSG" ;;
*) echo "Aborted." ;;
esac

The three-option prompt keeps you in control. Choosing edit opens your $EDITOR with the generated message pre-filled so you can tweak before committing.

Release Notes Generator

Generate structured release notes from a range of commits. This recipe uses --json-schema to enforce a consistent format that downstream tools (a static site, a Slack bot, an email template) can consume directly.

#!/bin/bash
# release-notes.sh -- Generate release notes between two tags
set -euo pipefail
FROM="${1:-$(git describe --tags --abbrev=0)}"
TO="${2:-HEAD}"
COMMITS=$(git log "$FROM".."$TO" --oneline)
[ -z "$COMMITS" ] && { echo "No commits between $FROM and $TO"; exit 1; }
CLAUDECODE="" claude -p "Generate release notes from these commits:
$COMMITS
Categorize each change as feature, fix, refactor, docs, or chore." \
--output-format json \
--json-schema '{
"type": "object",
"properties": {
"version": {"type": "string"},
"date": {"type": "string"},
"sections": {
"type": "array",
"items": {
"type": "object",
"properties": {
"category": {"type": "string"},
"items": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["category", "items"]
}
}
},
"required": ["version", "sections"]
}' \
--max-budget-usd 0.30 \
--no-session-persistence \
--permission-mode bypassPermissions \
--tools ""

Remember that --json-schema produces a double-encoded result: the result field in the JSON envelope is itself a JSON string. Parse it twice: jq '.result | fromjson'.

PR Analyzer

Analyze a pull request for risks, complexity, and review suggestions. This recipe pipes gh pr diff directly into Claude with a --json-schema to get structured, machine-readable output.

#!/bin/bash
# pr-analyzer.sh -- Analyze a PR for risks and suggestions
set -euo pipefail
PR_NUM="${1:?Usage: pr-analyzer.sh <pr-number>}"
DIFF=$(gh pr diff "$PR_NUM")
PR_INFO=$(gh pr view "$PR_NUM" --json title,body,additions,deletions)
CLAUDECODE="" claude -p "Analyze this pull request:
PR Info:
$PR_INFO
Diff:
$DIFF
Evaluate: risk level, test coverage gaps, and review suggestions." \
--output-format json \
--json-schema '{
"type": "object",
"properties": {
"risk_level": {"type": "string", "enum": ["low", "medium", "high"]},
"summary": {"type": "string"},
"concerns": {
"type": "array",
"items": {"type": "string"}
},
"suggestions": {
"type": "array",
"items": {"type": "string"}
},
"test_gaps": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["risk_level", "summary", "concerns", "suggestions"]
}' \
--max-budget-usd 0.50 \
--no-session-persistence \
--permission-mode bypassPermissions \
--tools ""

Pipe the output to jq for quick triage: ./pr-analyzer.sh 42 | jq '.result | fromjson | .risk_level'. In CI, you can gate merges on the risk_level field.

Documentation Generator

Generate API documentation from source code. This recipe uses --allowedTools to restrict Claude to read-only operations plus Write for the output file. It cannot run Bash or Edit existing files.

#!/bin/bash
# generate-docs.sh -- Generate API documentation from source
set -euo pipefail
SRC_DIR="${1:-.}"
OUTPUT="${2:-API.md}"
CLAUDECODE="" claude -p "Read source files in $SRC_DIR and generate API documentation in Markdown. Include function signatures, parameter types, return types, usage examples, and error conditions. Write output to $OUTPUT." \
--output-format json --max-budget-usd 1.00 \
--no-session-persistence --permission-mode bypassPermissions \
--allowedTools "Read,Grep,Glob,Write"

The --allowedTools flag is the key safety mechanism here. Claude can explore the codebase with Read, Grep, and Glob, and it can write the documentation file, but it cannot execute arbitrary commands or modify existing source files. This makes the script safe to run in unattended pipelines.

Budget-Safe Runner

Run multiple prompts from a file with aggregate budget tracking. Each prompt gets a shrinking budget cap so the total never exceeds your limit.

#!/bin/bash
# budget-runner.sh -- Run prompts with total budget cap
set -euo pipefail
MAX_TOTAL="${1:-5.00}"
TOTAL_SPENT=0
while IFS= read -r prompt; do
[ -z "$prompt" ] && continue
REMAINING=$(echo "$MAX_TOTAL - $TOTAL_SPENT" | bc)
REMAINING=$(echo "if ($REMAINING < 0.05) 0.05 else $REMAINING" | bc)
[ "$(echo "$TOTAL_SPENT >= $MAX_TOTAL" | bc)" -eq 1 ] && {
echo "Budget exhausted (\$$TOTAL_SPENT / \$$MAX_TOTAL)."; break; }
RESULT=$(CLAUDECODE="" claude -p "$prompt" \
--output-format json --max-budget-usd "$REMAINING" \
--no-session-persistence --permission-mode bypassPermissions)
if ! echo "$RESULT" | jq empty 2>/dev/null; then
echo "Invalid JSON response for: $prompt" >&2; continue
fi
COST=$(echo "$RESULT" | jq -r '.total_cost_usd // 0')
TOTAL_SPENT=$(echo "$TOTAL_SPENT + $COST" | bc)
echo "$RESULT" | jq -r '.result // ""'
echo "--- Cost: \$$COST | Total: \$$TOTAL_SPENT / \$$MAX_TOTAL ---"
done < prompts.txt

Note that --max-budget-usd is checked between turns, not mid-generation. A single-turn response can overshoot the limit. The aggregate tracking loop above is the real safety net. Keep individual prompt budgets low and check TOTAL_SPENT after each iteration.

Plan, Review, Execute Recipe

This is the safest pattern for automated refactoring. It splits the work into three phases: Claude plans in restricted mode, a human reviews the plan, and then Claude executes with full permissions by resuming the same session.

#!/bin/bash
# plan-execute.sh -- Plan mode -> review -> execute
set -euo pipefail
TASK="$1"
# Phase 1: Plan (blocks all writes)
echo "=== Planning ==="
PLAN=$(CLAUDECODE="" claude -p "$TASK" \
--permission-mode plan --output-format json --max-budget-usd 0.50)
SESSION=$(echo "$PLAN" | jq -r '.session_id')
echo "$PLAN" | jq -r '.result // ""'
# Phase 2: Human review
read -p "Approve and execute? (yes/no): " DECISION
[ "$DECISION" = "yes" ] || { echo "Aborted. Resume later: claude --resume $SESSION"; exit 0; }
# Phase 3: Execute with full permissions
echo "=== Executing ==="
CLAUDECODE="" claude -p "Proceed with the plan." \
--resume "$SESSION" --permission-mode bypassPermissions \
--output-format text --max-budget-usd 2.00

Phase 1 uses --permission-mode plan, which blocks all write operations. Claude can read files and reason about the task, but it cannot modify anything. The session_id from the planning phase carries all of that context forward, so Phase 3 does not need to re-analyze the codebase. If you decline, the script prints the session ID so you can resume later with claude --resume.

Batch Data Extractor

Extract structured data from multiple files using a JSON schema. This recipe loops over input files, sends each one to Claude with --json-schema, and tracks cost per file.

#!/bin/bash
# extract-data.sh -- Extract structured data from text files
set -euo pipefail
PATTERN="${1:-docs/*.md}"
BUDGET_PER_FILE="${2:-0.20}"
TOTAL_COST=0
for file in $PATTERN; do
[ -f "$file" ] || continue
echo "Processing: $file"
CONTENT=$(cat "$file")
RESULT=$(CLAUDECODE="" claude -p "Extract all person records from this text:
$CONTENT" \
--output-format json \
--json-schema '{
"type": "object",
"properties": {
"people": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"role": {"type": "string"},
"company": {"type": "string"}
},
"required": ["name"]
}
}
},
"required": ["people"]
}' \
--max-budget-usd "$BUDGET_PER_FILE" \
--no-session-persistence \
--permission-mode bypassPermissions 2>/dev/null) || { echo " FAILED"; continue; }
if ! echo "$RESULT" | jq empty 2>/dev/null; then
echo " Invalid JSON response"; continue
fi
COST=$(echo "$RESULT" | jq -r '.total_cost_usd // 0')
TOTAL_COST=$(echo "$TOTAL_COST + $COST" | bc)
echo " Cost: \$$COST (Running total: \$$TOTAL_COST)"
done
echo "=== Batch complete. Total cost: \$$TOTAL_COST ==="

Swap the prompt and schema to extract whatever structure you need: invoice line items, API endpoints from README files, dependency lists from changelogs. The loop skeleton stays the same.

Multi-Repo Status

Check status across multiple repositories and generate an executive summary. This recipe uses --tools "" because it is pure text analysis — no file access needed, which reduces system prompt cost.

#!/bin/bash
# repo-status.sh -- Check status across repos
set -euo pipefail
REPORT=""
for repo in "$@"; do
[ -d "$repo/.git" ] || continue
BRANCH=$(git -C "$repo" branch --show-current 2>/dev/null || echo "detached")
CHANGES=$(git -C "$repo" status --short | wc -l | tr -d ' ')
BEHIND=$(git -C "$repo" rev-list --count HEAD..origin/main 2>/dev/null || echo "?")
REPORT+="## $(basename "$repo")\n- Branch: $BRANCH\n- Uncommitted: $CHANGES\n- Behind main: $BEHIND\n\n"
done
[ -z "$REPORT" ] && { echo "No repos found."; exit 1; }
CLAUDECODE="" claude -p "Summarize this multi-repo status report. Flag anything needing attention:
$REPORT" \
--output-format text --max-budget-usd 0.20 \
--no-session-persistence --permission-mode bypassPermissions --tools ""

Run it with paths to your repos: ./repo-status.sh ~/projects/api ~/projects/frontend ~/projects/infra. The shell gathers all the git metadata before Claude ever runs, so the AI call is a single cheap text-analysis turn.

Script Skeleton

Every recipe above follows the same skeleton. When you write your own, start here:

#!/bin/bash
set -euo pipefail
# 1. Gather context
CONTEXT=$(git diff --cached)
# 2. Pass to Claude
CLAUDECODE="" claude -p "Review this diff: $CONTEXT" \
--output-format text \
--max-budget-usd 0.50 \
--no-session-persistence \
--permission-mode bypassPermissions

The three required flags for unattended scripts are --no-session-persistence (prevents session files from accumulating on disk), --permission-mode bypassPermissions (prevents the script from hanging on interactive prompts), and --max-budget-usd (prevents runaway costs). The CLAUDECODE="" prefix is covered below.

Gotcha

The CLAUDECODE="" environment variable prefix is required when calling claude from inside a shell script. Without it, nested Claude invocations detect the parent process and fail. Always prefix your claude commands with CLAUDECODE="" in scripts.

Gotcha

Always start your recipe scripts with set -euo pipefail. Without it, a failed git command or empty variable will silently pass garbage to Claude, waste tokens, and produce nonsense output. The -e flag exits on error, -u catches unset variables, and -o pipefail propagates failures through pipes.