Skip to content

Sync Anthropic Release Notes #86

Sync Anthropic Release Notes

Sync Anthropic Release Notes #86

name: Sync Anthropic Release Notes
on:
schedule:
# Runs once every 24h at 00:15 UTC
- cron: "15 0 * * *"
workflow_dispatch:
inputs:
force_update:
description: 'Force update all files'
required: false
type: boolean
default: false
permissions:
contents: write
concurrency:
group: sync-release-notes
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
persist-credentials: true
fetch-depth: 0
- name: Download and process release notes
id: sync_files
env:
FORCE_UPDATE: ${{ github.event.inputs.force_update || 'false' }}
run: |
set -euo pipefail
# Configuration
readonly FOLDER="Official Claude Releases"
readonly MAX_RETRIES=3
readonly RETRY_DELAY=5
# Color output for better readability in logs
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color
# Ensure folder exists
mkdir -p "$FOLDER"
# Source URLs mapped to destination filenames
declare -A FILES=(
["https://raw.githubusercontent.com/anthropics/claude-code/main/CHANGELOG.md"]="CHANGELOG.md"
["https://docs.anthropic.com/claude/docs/releases/api"]="api.md"
["https://docs.anthropic.com/claude/docs/releases/claude-apps"]="claude-apps.md"
["https://docs.anthropic.com/claude/docs/releases/system-prompts"]="system-prompts.md"
["https://docs.anthropic.com/claude/docs/releases/claude-code"]="claude-code.md"
)
# Function to download with retries
download_with_retry() {
local url="$1"
local output="$2"
local attempt=1
while [[ $attempt -le $MAX_RETRIES ]]; do
if curl -fsSL \
-H "Accept: text/markdown, text/plain;q=0.9, text/x-markdown;q=0.9, */*;q=0.1" \
-H "User-Agent: GitHub-Actions-Release-Notes-Sync/1.0" \
--connect-timeout 10 \
--max-time 30 \
"$url" -o "$output"; then
return 0
fi
echo -e "${YELLOW}Attempt $attempt/$MAX_RETRIES failed for $url${NC}" >&2
if [[ $attempt -lt $MAX_RETRIES ]]; then
sleep $RETRY_DELAY
fi
((attempt++))
done
echo -e "${RED}Failed to download $url after $MAX_RETRIES attempts${NC}" >&2
return 1
}
# Validate downloaded content
validate_content() {
local file="$1"
local min_size=10
# Check file exists and has content
if [[ ! -f "$file" ]] || [[ ! -s "$file" ]]; then
echo -e "${RED}File $file is empty or doesn't exist${NC}" >&2
return 1
fi
# Check minimum size
local size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
if [[ $size -lt $min_size ]]; then
echo -e "${RED}File $file is too small ($size bytes)${NC}" >&2
return 1
fi
return 0
}
# Arrays to track changes
declare -a CHANGED_FILES=()
COMMIT_BODY=""
README_LIST=""
TOTAL_ADDED=0
TOTAL_DELETED=0
# Process each file
for url in "${!FILES[@]}"; do
name="${FILES[$url]}"
out="$FOLDER/$name"
tmp="$out.tmp"
echo -e "${GREEN}Processing: $name${NC}"
# Download file
if ! download_with_retry "$url" "$tmp"; then
echo -e "${RED}Skipping $name due to download failure${NC}"
rm -f "$tmp"
continue
fi
# Validate downloaded content
if ! validate_content "$tmp"; then
echo -e "${RED}Skipping $name due to validation failure${NC}"
rm -f "$tmp"
continue
fi
# Check if file needs updating
needs_update=false
if [[ "$FORCE_UPDATE" == "true" ]]; then
needs_update=true
echo "Force update enabled"
elif [[ ! -f "$out" ]]; then
needs_update=true
echo "New file detected"
elif ! git --no-pager diff --no-index --ignore-all-space --quiet -- "$out" "$tmp" 2>/dev/null; then
needs_update=true
echo "Changes detected"
fi
if [[ "$needs_update" == "false" ]]; then
echo "No changes for $name"
rm -f "$tmp"
continue
fi
# Calculate statistics
base_file="$out"
[[ -f "$out" ]] || base_file="/dev/null"
numstat=$(git --no-pager diff --no-index --numstat -- "$base_file" "$tmp" 2>/dev/null || echo "0 0 -")
read -r added deleted _ <<< "$numstat"
# Extract highlights from changes
highlights=$(
git --no-pager diff --no-index -U0 -- "$base_file" "$tmp" 2>/dev/null \
| sed -n 's/^+[^+]//p' \
| grep -E '^(#{1,3} |- |\* |[0-9]+\. )' \
| head -n 5 || true
)
# Fallback to first few lines if no structured content
if [[ -z "$highlights" ]]; then
highlights=$(
git --no-pager diff --no-index -U0 -- "$base_file" "$tmp" 2>/dev/null \
| sed -n 's/^+[^+]//p' \
| grep -v '^$' \
| head -n 3 || true
)
fi
# Update file and track changes
mv -f "$tmp" "$out"
CHANGED_FILES+=("$out")
# Build commit message body
COMMIT_BODY+="- **${name}** (+${added:-0} / -${deleted:-0})"$'\n'
if [[ -n "$highlights" ]]; then
while IFS= read -r line; do
# Truncate long lines
line="${line:0:100}"
COMMIT_BODY+=" • ${line}"$'\n'
done <<< "$highlights"
fi
# Build README summary
README_LIST+="- **${name}**: +${added:-0} / -${deleted:-0}"$'\n'
# Track totals
((TOTAL_ADDED += added)) || true
((TOTAL_DELETED += deleted)) || true
echo -e "${GREEN}✓ Updated $name (+$added / -$deleted)${NC}"
done
# Stage changed files
if [[ ${#CHANGED_FILES[@]} -gt 0 ]]; then
git add "${CHANGED_FILES[@]}"
echo "files_changed=true" >> "$GITHUB_OUTPUT"
else
echo "files_changed=false" >> "$GITHUB_OUTPUT"
fi
# Save data for next steps
{
printf '%b' "$COMMIT_BODY"
} > /tmp/commit_body.txt
{
printf '%b' "$README_LIST"
} > /tmp/readme_list.txt
echo "total_added=$TOTAL_ADDED" >> "$GITHUB_OUTPUT"
echo "total_deleted=$TOTAL_DELETED" >> "$GITHUB_OUTPUT"
echo "changed_count=${#CHANGED_FILES[@]}" >> "$GITHUB_OUTPUT"
- name: Update status README
run: |
set -euo pipefail
readonly FOLDER="Official Claude Releases"
readonly README="$FOLDER/README.md"
readonly STATUS_START="<!-- sync-status:start -->"
readonly STATUS_END="<!-- sync-status:end -->"
# Get timestamps
date_utc=$(date -u +'%Y-%m-%d %H:%M:%S %Z')
date_oslo=$(TZ=Europe/Oslo date +'%Y-%m-%d %H:%M:%S %Z')
readme_list=$(cat /tmp/readme_list.txt 2>/dev/null || true)
# Build status block
{
echo "$STATUS_START"
echo
echo "### 🔄 Last Sync"
echo
echo "- **UTC**: \`$date_utc\`"
echo "- **Europe/Oslo**: \`$date_oslo\`"
echo "- **Workflow Run**: [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
echo
if [[ -n "$readme_list" ]]; then
echo "### ✅ Files Updated This Run"
echo
printf '%b' "$readme_list"
echo
echo "**Total changes**: +${{ steps.sync_files.outputs.total_added }} / -${{ steps.sync_files.outputs.total_deleted }} lines"
else
echo "### ℹ️ No Changes Detected"
echo
echo "All tracked files are up to date."
fi
echo
echo "$STATUS_END"
} > /tmp/status_block.md
# Create README if it doesn't exist
if [[ ! -f "$README" ]]; then
mkdir -p "$FOLDER"
{
echo "# Official Claude Releases"
echo
echo "This folder automatically syncs selected Anthropic release notes for easy tracking."
echo
echo "## 📋 Tracked Files"
echo
echo "- **CHANGELOG.md**: Claude Code changelog"
echo "- **api.md**: Claude API release notes"
echo "- **claude-apps.md**: Claude Apps (Web/Mobile/Desktop) release notes"
echo "- **system-prompts.md**: System prompt updates"
echo "- **claude-code.md**: Claude Code release notes from docs"
echo
echo "## 🤖 Automation"
echo
echo "- Runs daily at 00:15 UTC via GitHub Actions"
echo "- Can be manually triggered with optional force update"
echo "- Only commits when actual content changes are detected"
echo
} > "$README"
fi
# Update or append status block
if grep -q "$STATUS_START" "$README"; then
# Replace existing status block
perl -0777 -i -pe '
BEGIN {
undef $/;
open my $fh, "<", "/tmp/status_block.md" or die;
our $block = <$fh>;
close $fh;
}
s/<!-- sync-status:start -->.*?<!-- sync-status:end -->/$block/s
' "$README"
else
# Append new status block
{
echo
echo "---"
echo
cat /tmp/status_block.md
} >> "$README"
fi
# Always stage README
git add "$README"
- name: Commit and push changes
if: always()
run: |
set -euo pipefail
# Configure git
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Check if there are changes to commit
if ! git diff --cached --quiet; then
date_str=$(date -u +%Y-%m-%d)
if [[ "${{ steps.sync_files.outputs.files_changed }}" == "true" ]]; then
# Content changes detected
title="🔄 Sync release notes ($date_str)"
body=$(cat /tmp/commit_body.txt 2>/dev/null || echo "Updated release notes")
git commit -m "$title" -m "$body"
echo "✅ Committed content changes"
else
# Only README status update
title="📊 Update sync status ($date_str)"
body="No release note content changes. Updated status in README."
git commit -m "$title" -m "$body"
echo "✅ Committed status update"
fi
# Push changes
git push
echo "✅ Pushed changes to repository"
else
echo "ℹ️ No changes to commit"
fi
- name: Create summary
if: always()
run: |
{
echo "## 📊 Sync Summary"
echo
if [[ "${{ steps.sync_files.outputs.files_changed }}" == "true" ]]; then
echo "### ✅ Success"
echo
echo "**Files updated**: ${{ steps.sync_files.outputs.changed_count }}"
echo "**Lines added**: +${{ steps.sync_files.outputs.total_added }}"
echo "**Lines deleted**: -${{ steps.sync_files.outputs.total_deleted }}"
echo
echo "### 📝 Changes"
echo
echo '```'
cat /tmp/commit_body.txt 2>/dev/null || echo "No details available"
echo '```'
else
echo "### ℹ️ No Changes"
echo
echo "All tracked files are up to date. Status README was updated."
fi
} >> "$GITHUB_STEP_SUMMARY"