Created
January 13, 2026 16:26
-
-
Save scgreenhalgh/59257c031124cea344cb2c60de4f683d to your computer and use it in GitHub Desktop.
Fix Claude Code's Outdated Solutions Problem Claude Code doesn't know the current date, so it often suggests deprecated APIs, old package versions, and outdated Stack Overflow solutions. This wrapper script fixes that by automatically injecting date context into your CLAUDE.md files. Every time you run `claude`, it updates your global CLAUDE.md …
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # setup-claude-date-awareness.sh | |
| # | |
| # Automatically injects current date context into Claude Code's CLAUDE.md files | |
| # so Claude uses up-to-date solutions and documentation. | |
| # | |
| # Tested on: macOS (zsh/bash), Linux (bash/zsh/fish) | |
| # | |
| # Usage: | |
| # chmod +x setup-claude-date-awareness.sh | |
| # ./setup-claude-date-awareness.sh | |
| # source ~/.zshrc # or ~/.bashrc, ~/.config/fish/config.fish | |
| set -e | |
| CLAUDE_DIR="$HOME/.claude" | |
| WRAPPER_SCRIPT="$CLAUDE_DIR/claude-wrapper.sh" | |
| DATEFILE="$CLAUDE_DIR/current_date.txt" | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Detect shell and rc file | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| detect_shell() { | |
| local user_shell | |
| user_shell=$(basename "$SHELL") | |
| case "$user_shell" in | |
| zsh) | |
| RC_FILE="${ZDOTDIR:-$HOME}/.zshrc" | |
| SHELL_TYPE="posix" | |
| ;; | |
| bash) | |
| if [[ "$OSTYPE" == darwin* ]]; then | |
| RC_FILE="$HOME/.bash_profile" | |
| else | |
| RC_FILE="$HOME/.bashrc" | |
| fi | |
| SHELL_TYPE="posix" | |
| ;; | |
| fish) | |
| RC_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/fish/config.fish" | |
| SHELL_TYPE="fish" | |
| ;; | |
| ksh|mksh) | |
| RC_FILE="$HOME/.kshrc" | |
| SHELL_TYPE="posix" | |
| ;; | |
| *) | |
| RC_FILE="$HOME/.profile" | |
| SHELL_TYPE="posix" | |
| echo "⚠ Unknown shell '$user_shell', using $RC_FILE" | |
| ;; | |
| esac | |
| echo "Detected shell: $user_shell" | |
| echo "RC file: $RC_FILE" | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Create the wrapper script | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| create_wrapper_script() { | |
| mkdir -p "$CLAUDE_DIR" | |
| [[ -f "$WRAPPER_SCRIPT" ]] && cp "$WRAPPER_SCRIPT" "$WRAPPER_SCRIPT.backup.$(date +%s)" | |
| cat > "$WRAPPER_SCRIPT" << 'WRAPPER' | |
| #!/usr/bin/env bash | |
| # Claude Code date-aware wrapper | |
| # Auto-generated by setup-claude-date-awareness.sh | |
| set -e | |
| CLAUDE_DIR="$HOME/.claude" | |
| GLOBAL_MD="$CLAUDE_DIR/CLAUDE.md" | |
| PROJECT_MD="./CLAUDE.md" | |
| DATEFILE="$CLAUDE_DIR/current_date.txt" | |
| WRAPPER_PATH="$CLAUDE_DIR/claude-wrapper.sh" | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Portable path resolution (macOS lacks realpath by default) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| resolve_path() { | |
| local target="$1" | |
| # Try GNU realpath first | |
| if command -v realpath >/dev/null 2>&1; then | |
| realpath "$target" 2>/dev/null && return 0 | |
| fi | |
| # Try GNU readlink -f (not available on macOS BSD readlink) | |
| if [[ "$(uname)" != "Darwin" ]] && command -v readlink >/dev/null 2>&1; then | |
| readlink -f "$target" 2>/dev/null && return 0 | |
| fi | |
| # macOS/BSD fallback using perl (standard on macOS) | |
| if command -v perl >/dev/null 2>&1; then | |
| perl -e 'use Cwd "abs_path"; print abs_path(shift)' "$target" 2>/dev/null && return 0 | |
| fi | |
| # Last resort: manual resolution | |
| if [[ -e "$target" ]]; then | |
| local dir base | |
| dir=$(cd "$(dirname "$target")" 2>/dev/null && pwd) | |
| base=$(basename "$target") | |
| echo "${dir}/${base}" | |
| return 0 | |
| fi | |
| # If file doesn't exist, return input unchanged | |
| echo "$target" | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Get current date (portable across macOS and Linux) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| current_date=$(date +"%Y-%m-%d") | |
| current_year=$(date +"%Y") | |
| if last_year=$(date -v-1y +"%Y" 2>/dev/null); then | |
| : # macOS BSD date | |
| elif last_year=$(date -d "1 year ago" +"%Y" 2>/dev/null); then | |
| : # GNU date | |
| else | |
| last_year=$((current_year - 1)) # Arithmetic fallback | |
| fi | |
| mkdir -p "$CLAUDE_DIR" | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Date block content | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| DATE_BLOCK_CONTENT="## Current Date Context (Auto-Updated) | |
| - Today: ${current_date} | |
| - When searching docs/solutions: prefer ${current_year}-${last_year} sources | |
| - Treat pre-2023 solutions with skepticism unless for stable/legacy APIs" | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Update CLAUDE.md - preserves marker position in file | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| update_claude_md() { | |
| local target="$1" | |
| local temp_file | |
| temp_file=$(mktemp) | |
| # Ensure cleanup on any exit from this function | |
| trap "rm -f '$temp_file'" RETURN | |
| if grep -q "<!-- AUTO_DATE -->" "$target" 2>/dev/null; then | |
| # Markers exist: replace content BETWEEN markers, preserving position | |
| # Using environment variable + e modifier for safe multiline handling | |
| DATE_BLOCK_CONTENT="$DATE_BLOCK_CONTENT" perl -0777 -pe ' | |
| s/(<!-- AUTO_DATE -->).*?(<!-- \/AUTO_DATE -->)/$1 . "\n" . $ENV{DATE_BLOCK_CONTENT} . "\n" . $2/se | |
| ' "$target" > "$temp_file" | |
| # Verify the output is valid | |
| if [[ -s "$temp_file" ]] && grep -q "<!-- AUTO_DATE -->" "$temp_file"; then | |
| mv "$temp_file" "$target" | |
| trap - RETURN # Clear trap since we moved the file | |
| else | |
| echo "Warning: Failed to update $target (perl output invalid)" >&2 | |
| return 1 | |
| fi | |
| elif [[ "$target" == "$GLOBAL_MD" ]]; then | |
| # Global file without markers: prepend full block with markers | |
| { | |
| echo "<!-- AUTO_DATE -->" | |
| echo "$DATE_BLOCK_CONTENT" | |
| echo "<!-- /AUTO_DATE -->" | |
| echo "" | |
| cat "$target" 2>/dev/null || true | |
| } > "$temp_file" | |
| # Verify and move | |
| if [[ -s "$temp_file" ]]; then | |
| mv "$temp_file" "$target" | |
| trap - RETURN | |
| else | |
| echo "Warning: Failed to create date block for $target" >&2 | |
| return 1 | |
| fi | |
| fi | |
| # For project files without markers: do nothing (opt-in only) | |
| # temp_file cleanup handled by trap | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Update date file and CLAUDE.md files | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # 1. Always update datefile | |
| printf 'Date: %s\nYear: %s\nPrefer sources from: %s-%s\n' \ | |
| "$current_date" "$current_year" "$current_year" "$last_year" > "$DATEFILE" | |
| # 2. Update global CLAUDE.md (create if missing) | |
| if [[ -f "$GLOBAL_MD" ]]; then | |
| update_claude_md "$GLOBAL_MD" || true | |
| else | |
| # Create new global file with markers | |
| { | |
| echo "<!-- AUTO_DATE -->" | |
| echo "$DATE_BLOCK_CONTENT" | |
| echo "<!-- /AUTO_DATE -->" | |
| } > "$GLOBAL_MD" | |
| fi | |
| # 3. Update project CLAUDE.md only if opt-in markers exist | |
| if [[ -f "$PROJECT_MD" ]] && grep -q "<!-- AUTO_DATE -->" "$PROJECT_MD" 2>/dev/null; then | |
| update_claude_md "$PROJECT_MD" || true | |
| fi | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Find the real claude binary (handles spaces, avoids infinite loop) | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| find_real_claude() { | |
| local wrapper_resolved | |
| wrapper_resolved=$(resolve_path "$WRAPPER_PATH") | |
| # Explicit paths (no globs - handles spaces correctly) | |
| local explicit_paths=( | |
| "$HOME/.claude/local/bin/claude" | |
| "$HOME/.local/bin/claude" | |
| "/usr/local/bin/claude" | |
| "/opt/homebrew/bin/claude" | |
| "/usr/bin/claude" | |
| "/opt/local/bin/claude" | |
| "$HOME/.npm-global/bin/claude" | |
| ) | |
| local all_candidates=() | |
| # Add explicit paths | |
| for p in "${explicit_paths[@]}"; do | |
| all_candidates+=("$p") | |
| done | |
| # Add nvm paths using find with null delimiter (handles spaces safely) | |
| if [[ -d "$HOME/.nvm/versions/node" ]]; then | |
| while IFS= read -r -d '' nvm_claude; do | |
| all_candidates+=("$nvm_claude") | |
| done < <(find "$HOME/.nvm/versions/node" -maxdepth 4 -name claude -path "*/bin/claude" -print0 2>/dev/null || true) | |
| fi | |
| # Check all candidates | |
| for candidate in "${all_candidates[@]}"; do | |
| if [[ -x "$candidate" ]]; then | |
| local candidate_resolved | |
| candidate_resolved=$(resolve_path "$candidate") | |
| if [[ "$candidate_resolved" != "$wrapper_resolved" ]]; then | |
| echo "$candidate" | |
| return 0 | |
| fi | |
| fi | |
| done | |
| # Search PATH | |
| local IFS=':' | |
| for dir in $PATH; do | |
| [[ -z "$dir" ]] && continue | |
| local candidate="$dir/claude" | |
| if [[ -x "$candidate" ]]; then | |
| local candidate_resolved | |
| candidate_resolved=$(resolve_path "$candidate") | |
| if [[ "$candidate_resolved" != "$wrapper_resolved" ]]; then | |
| echo "$candidate" | |
| return 0 | |
| fi | |
| fi | |
| done | |
| return 1 | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Execute real claude | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| REAL_CLAUDE=$(find_real_claude) || { | |
| echo "Error: Could not find claude binary." >&2 | |
| echo "Make sure Claude Code is installed: https://docs.anthropic.com/en/docs/claude-code" >&2 | |
| exit 1 | |
| } | |
| exec "$REAL_CLAUDE" "$@" | |
| WRAPPER | |
| chmod +x "$WRAPPER_SCRIPT" | |
| echo "✓ Created wrapper script: $WRAPPER_SCRIPT" | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Add shell integration | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| add_shell_integration() { | |
| local marker="# Claude date-aware wrapper" | |
| if grep -qF "$marker" "$RC_FILE" 2>/dev/null; then | |
| echo "⚠ Shell integration already exists in $RC_FILE" | |
| echo " To reinstall, remove the existing claude function first." | |
| return 1 | |
| fi | |
| mkdir -p "$(dirname "$RC_FILE")" | |
| touch "$RC_FILE" | |
| cp "$RC_FILE" "$RC_FILE.backup.$(date +%s)" | |
| case "$SHELL_TYPE" in | |
| posix) | |
| cat >> "$RC_FILE" << EOF | |
| $marker | |
| claude() { | |
| "$WRAPPER_SCRIPT" "\$@" | |
| } | |
| EOF | |
| ;; | |
| fish) | |
| cat >> "$RC_FILE" << EOF | |
| $marker | |
| function claude | |
| $WRAPPER_SCRIPT \$argv | |
| end | |
| EOF | |
| ;; | |
| esac | |
| echo "✓ Added shell integration to $RC_FILE" | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Backup existing CLAUDE.md | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| backup_claude_md() { | |
| if [[ -f "$CLAUDE_DIR/CLAUDE.md" ]]; then | |
| cp "$CLAUDE_DIR/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md.backup.$(date +%s)" | |
| echo "✓ Backed up existing global CLAUDE.md" | |
| fi | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Print instructions | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| print_instructions() { | |
| cat << EOF | |
| ════════════════════════════════════════════════════════════════════ | |
| SETUP COMPLETE | |
| ════════════════════════════════════════════════════════════════════ | |
| 1. Reload your shell config: | |
| source $RC_FILE | |
| Or open a new terminal window. | |
| 2. Run claude as normal: | |
| claude | |
| ════════════════════════════════════════════════════════════════════ | |
| FOR EXISTING PROJECTS WITH CLAUDE.md | |
| ════════════════════════════════════════════════════════════════════ | |
| Option A — Auto-update (opt-in): | |
| Add these two lines anywhere in your project's CLAUDE.md: | |
| <!-- AUTO_DATE --> | |
| <!-- /AUTO_DATE --> | |
| The date block fills in between markers. Position is preserved. | |
| Option B — Manual check (no file changes): | |
| Add to your project's CLAUDE.md: | |
| ## Date Awareness | |
| At session start, run: cat ~/.claude/current_date.txt | |
| ════════════════════════════════════════════════════════════════════ | |
| FILES | |
| ════════════════════════════════════════════════════════════════════ | |
| Wrapper: $WRAPPER_SCRIPT | |
| Date file: $DATEFILE | |
| Shell config: $RC_FILE | |
| ════════════════════════════════════════════════════════════════════ | |
| EOF | |
| } | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Main | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| main() { | |
| echo "Setting up Claude Code date-aware wrapper..." | |
| echo "" | |
| detect_shell | |
| echo "" | |
| backup_claude_md | |
| create_wrapper_script | |
| if add_shell_integration; then | |
| print_instructions | |
| else | |
| echo "" | |
| echo "Wrapper script updated at $WRAPPER_SCRIPT" | |
| fi | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment