Blog
automation

Automating Content Creation with Claude Code and GitHub Actions

How I automated font content creation with Claude Code and GitHub Actions, making the process 10x faster while handling font requests automatically.

Mladen Ruzicic
Mladen Ruzicic
8 min

Adding a new font to FontAlternatives used to take 30+ minutes:

  • Research the font (foundry, classification, traits)
  • Find free alternatives with similarity scores
  • Write the markdown file with proper frontmatter
  • Create bidirectional links to alternatives
  • Download specimen images
  • Run validation checks
  • Create PR and deploy

Now it takes 3 minutes. And I don’t do any of it manually.

The problem

Manual content creation doesn’t scale. With 6,198 fonts in my “unavailable” index generating demand signals, I needed a way to process font requests faster than I could type. The font requests that Claude Code processes come from the demand tracking pipeline.

Options I considered:

  • Hire writers: Expensive, training overhead, inconsistent quality
  • Template generators: Fast but generic, no research capability
  • AI with prompts: Flexible but requires manual invocation

What I needed: an AI agent that could research fonts, write content, and handle the full PR workflow autonomously.

The solution: Claude Code in GitHub Actions

Claude Code is Anthropic’s CLI agent that can read files, write code, run commands, and interact with APIs. I wired it into my GitHub Actions workflow.

flowchart TD
    A[Issue labeled 'font-request'] --> B[process-font-request.yml]

    subgraph workflow[GitHub Actions Workflow]
        B --> C[Create branch]
        C --> D[Run Claude Code]
        D --> E[Research font & write files]
        E --> F[Download specimen images]
        F --> G[Run guardrails]
        G --> H[Create PR]
        H --> I[Trigger preview deployment]
    end

    I --> J[Preview deployed]
    J --> K[PR ready for review]

    style A fill:#fff3e0
    style K fill:#c8e6c9

The workflow

Step 1: Issue trigger

When an issue gets the font-request label (either manually or from my demand bot):

on:
  issues:
    types: [labeled]

jobs:
  process:
    if: github.event.label.name == 'font-request'

Step 2: Branch creation

- name: Create branch
  run: |
    SLUG=$(echo "$ISSUE_TITLE" | sed 's/Font Request: //' | \
      tr '[:upper:]' '[:lower:]' | tr ' ' '-')
    git checkout -b "font-request/$SLUG"

Step 3: Claude Code invocation

This is the magic. I pass the issue body as context:

- name: Process with Claude Code
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
  run: |
    claude --print "
    Process this font request issue:

    ${{ github.event.issue.body }}

    Follow these steps:
    1. Research the font (foundry, classification, traits, use cases)
    2. Find 2-3 free alternatives on Google Fonts
    3. Calculate similarity scores based on visual characteristics
    4. Create the premium font markdown file
    5. Update free font files with back-references
    6. Remove from unavailable-fonts.json if present

    Use the content guide at docs/content-guide.md for formatting.
    This is a Tier 2 font (standard content depth).
    "

Step 4: Image download

Claude Code can run shell commands, so it triggers the image orchestrator:

npx tsx scripts/download-font-images.ts --slug avenir

The orchestrator tries foundry-specific scrapers first, then MyFonts, then generic fallback.

Step 5: Guardrails

Before creating the PR, validate everything:

- name: Run guardrails
  run: ./guardrails.sh --level 1

Level 1 checks:

  • Biome lint (formatting)
  • Schema validation (frontmatter matches Zod schemas)
  • Link validation (bidirectional links work)
  • Content checks (word counts, FAQ presence)

Step 6: PR creation

- name: Create PR
  env:
    GH_TOKEN: ${{ secrets.PAT_TOKEN }}  # Not GITHUB_TOKEN - see below
  run: |
    gh pr create \
      --title "feat: add $FONT_NAME font" \
      --body "Closes #$ISSUE_NUMBER ..."

Important: I use a Personal Access Token, not GITHUB_TOKEN. Why? GitHub Actions workflows triggered by GITHUB_TOKEN don’t trigger other workflows. I need this PR to trigger my preview deployment workflow.

Step 7: Preview deployment

The PR creation triggers preview.yml, which:

  1. Builds the site with the new font
  2. Uploads assets to R2
  3. Deploys to preview Workers environment
  4. Updates the PR description with preview URLs

The <!-- PREVIEW_URL_PLACEHOLDER --> comment gets replaced with:

| Status | Preview | Font Page |
|--------|---------|-----------|
| Ready | [Site](https://preview...) | [/alternatives/avenir/](https://preview.../alternatives/avenir/) |

The Claude Code prompt

The full prompt I use:

You are processing a font request for FontAlternatives.com.

## Issue Details
$ISSUE_BODY

## Instructions

1. **Research the font**
   - Identify the foundry (check MyFonts, official site)
   - Determine classification: sans-serif | serif | display | mono | handwritten
   - List traits: geometric, humanist, condensed, rounded, etc.
   - Note use cases: branding, editorial, ui, code, etc.
   - Check if it's a variable font

2. **Find free alternatives**
   - Search Google Fonts for similar fonts
   - Look for matching: classification, x-height, width, contrast
   - Calculate similarity scores (0-100) based on visual characteristics
   - Select 2-3 best matches

3. **Create premium font file**
   Location: src/content/premiumFonts/{slug}.md

   Required frontmatter:
   - name, slug, tier: "2", classification
   - foundry, traits, useCases
   - variableFont (boolean)
   - alternatives array with slug + similarity

   Content: 100+ words describing the font and alternatives

4. **Link free alternatives**
   - For each alternative in the premium font
   - Open src/content/freeFonts/{alt-slug}.md
   - Add premium slug to alternativeFor array

5. **Clean up**
   - Remove font from src/data/unavailable-fonts.json if present

6. **Download images**
   Run: npx tsx scripts/download-font-images.ts --slug {slug}

7. **Validate**
   Run: npm run check:fonts

Follow docs/content-guide.md for style and formatting.
Do not add emojis to files.
Use imperative commit messages.

Tradeoffs

What I gained:

  • 10x faster than manual (30 min to 3 min)
  • Consistent quality (follows my content guide exactly)
  • Scales with demand (can process 10 fonts/day)
  • I review PRs, not write content

What I lost:

  • Claude Code costs ~$0.50 per font (API calls)
  • Occasional errors require PR fixes
  • Less “voice” in content (template-y)

Quality control:

  • Guardrails catch schema/link errors before PR
  • Preview deployment lets me visually verify
  • I review every PR before merge (1-2 min review)

The PAT token trick

This was a gotcha that took hours to debug.

GitHub Actions workflows using GITHUB_TOKEN:

  • Can create PRs
  • Can push commits
  • Cannot trigger other workflows

From GitHub docs:

When you use the repository’s GITHUB_TOKEN to perform tasks, events triggered by the GITHUB_TOKEN will not create a new workflow run.

My solution: use a Personal Access Token (PAT) for the PR creation step:

- name: Create PR
  env:
    GH_TOKEN: ${{ secrets.PAT_TOKEN }}  # Not GITHUB_TOKEN

The PAT needs repo scope. Store it as a repository secret.

Cost analysis

ComponentCost per Font
Claude Code API~$0.30-0.50
GitHub ActionsFree (within limits)
Cloudflare WorkersFree tier
R2 StorageFree tier

At 3 fonts/day (my rate limit), worst case is ~$45/month in API costs. For context, that’s less than one hour of my time.

Results

Since implementing this system:

MetricBeforeAfter
Time per font30-45 min3-5 min (my review time)
Fonts added per week2-310-15
ConsistencyVariableHigh
My involvementFull processReview only

The pipeline has processed 89 font requests with a 94% first-pass success rate (guardrails catch most issues).

Complete workflow example

Here’s the essence of process-font-request.yml:

name: Process Font Request

on:
  issues:
    types: [labeled]

jobs:
  process:
    if: github.event.label.name == 'font-request'
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: "22"
          cache: "npm"

      - run: npm ci

      - name: Extract font info
        id: font
        run: |
          FONT_NAME=$(echo "${{ github.event.issue.title }}" | \
            sed 's/Font Request: //')
          SLUG=$(echo "$FONT_NAME" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
          echo "name=$FONT_NAME" >> $GITHUB_OUTPUT
          echo "slug=$SLUG" >> $GITHUB_OUTPUT

      - name: Create branch
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git checkout -b "font-request/${{ steps.font.outputs.slug }}"

      - name: Process with Claude Code
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          npx @anthropic-ai/claude-code --print "
          Process font request for: ${{ steps.font.outputs.name }}
          Issue body: ${{ github.event.issue.body }}
          Follow docs/content-guide.md.
          "

      - name: Run guardrails
        run: ./guardrails.sh --level 1

      - name: Commit and push
        run: |
          git add -A
          git commit -m "feat: add ${{ steps.font.outputs.name }}"
          git push -u origin "font-request/${{ steps.font.outputs.slug }}"

      - name: Create PR
        env:
          GH_TOKEN: ${{ secrets.PAT_TOKEN }}
        run: |
          gh pr create \
            --title "feat: add ${{ steps.font.outputs.name }}" \
            --body "Closes #${{ github.event.issue.number }}" \
            --label "automated"

This automation is a key part of the content flywheel.

What’s next

I’m considering:

  • Confidence scoring: Have Claude rate its own work, flag low-confidence PRs
  • Image verification: Computer vision to verify specimen images are correct
  • Auto-merge: If guardrails pass and preview looks good, merge automatically

But for now, the human review step (2 minutes) catches edge cases and gives me confidence in what ships.

Explore on FontAlternatives

#automation#ai#claude-code#github-actions#ci-cd

More from the blog