Module 1: Claude Code in GitHub Actions

Costs and Rate Limiting

Costs and Rate Limiting

Overview

Your workflow works, it's secure, and it produces useful output. But there's a question you haven't answered yet: how much does it cost? Each run consumes Anthropic API tokens + GitHub Actions runner minutes. Without control, costs grow fast — and the first bill surprises the team.

This capsule teaches you to budget and optimize. You'll learn to calculate the real cost per run with your model and token volume, identify which triggers generate unnecessary runs, configure path filters to run only when it's relevant, and apply rate limiting at the workflow level so that a bad day (50 PRs in a problematic repo) doesn't blow up your budget.

By the end, you'll have an economically sustainable workflow: predictable, optimized, and with guards for the extreme cases.


The Basic Cost Calculation

A Claude Code run in CI has two cost components:

COST PER RUN = api_cost + runner_cost

Anthropic API:
  Input tokens × input_price + Output tokens × output_price
  (official prices at docs.anthropic.com/pricing)

GitHub Actions runner:
  minutes × price_per_minute (free tier vs paid)
  (Linux: $0.008/min on private repos after the free tier;
   free unlimited on public repos)

Concrete example

Let's assume a workflow that runs claude-haiku-4-5 with an average PR:

Average input:  3,000 tokens (diff + prompt)
Average output: 600 tokens   (structured analysis)

Per run:
  3,000 × $0.0008/1K + 600 × $0.004/1K
  = $0.0024 + $0.0024
  = ~$0.005 per API call (half a cent)

Runner:
  ~1.5 minutes × $0.008/min
  = $0.012

Total per run: ~$0.017 (~1.7 cents)

Scaling to a team

Team of 5 developers, 10 PRs per week each, 3 updates on average per PR:

PRs per week:          50
Updates:               150 (3 per PR)
Total runs/week:       ~150
Runs/month:            ~600

Monthly cost:
  600 × $0.017 = ~$10.20/month

Scalable for a small team. But there are cases where it explodes:

Large team with a monorepo:
  20 developers × 15 PRs/week × 5 updates = 1,500 runs/week
  6,000 runs/month × $0.017 = ~$100/month

If on top of that you use Sonnet or Opus (10× more expensive):
  6,000 × $0.17 = ~$1,000/month

The model choice is the #1 cost factor. Haiku is 10× cheaper than Sonnet. For general analysis (this module), Haiku is enough. For complex refactorings, Sonnet is worth it. Opus only for critical reasoning tasks.


Path Filters: Run Only When It's Relevant

The first big saving: don't run the workflow on irrelevant changes. A PR that only modifies README.md doesn't need Claude Code analysis.

Configuring path filters

on:
  pull_request:
    types: [opened, synchronize]
    paths:
      - 'src/**'
      - 'lib/**'
      - 'tests/**'
      - 'package.json'
      - 'requirements.txt'
    paths-ignore:
      - '**.md'
      - 'docs/**'
      - '.github/ISSUE_TEMPLATE/**'

How it works:

  • paths: only triggers if the PR touches at least one of those paths
  • paths-ignore: doesn't trigger if all the modified files are in those paths

Rule: use one or the other, not both. paths-ignore is more conservative (runs by default and excludes cases); paths is more restrictive (only runs on what's allowed).

Typical use case

# Run only when source code or tests change
paths:
  - 'src/**'
  - 'tests/**'

# But ignore minor changes
paths-ignore:
  - 'src/**.md'
  - 'tests/fixtures/**'

Estimated saving: in a project with active docs, path filters reduce runs by 30-50%.


Conditional Skipping with if

Beyond paths, you can skip runs based on PR conditions:

Example: skip bot PRs

jobs:
  analyze:
    if: github.actor != 'dependabot[bot]' && github.actor != 'renovate[bot]'
    runs-on: ubuntu-latest
    steps:
      # ...

Bots like Dependabot open many dependency-update PRs. Analyzing them with Claude Code rarely adds value (the diffs are predictable version changes).

Example: skip draft PRs

jobs:
  analyze:
    if: github.event.pull_request.draft == false
    runs-on: ubuntu-latest

A draft PR is a work in progress. Analyzing before the author marks it as ready wastes runs.

Example: skip if the commit message says so

jobs:
  analyze:
    if: "!contains(github.event.head_commit.message, '[skip ai]')"
    runs-on: ubuntu-latest

Lets developers skip the analysis when they know it doesn't add value (e.g. "trivial copy update"). Convention: include [skip ai] in the commit message.


Limiting the Diff Size

A PR with 500 modified files can generate a huge diff — expensive and not very useful for Claude Code (quality degrades with a full context).

Validate the size before running

"""Validate the size before calling Claude."""
from pathlib import Path
import sys

MAX_DIFF_LINES = 1500  # reasonable threshold

diff_text = Path("pr_diff.txt").read_text()
diff_lines = len(diff_text.splitlines())

if diff_lines > MAX_DIFF_LINES:
    print(f"PR too large ({diff_lines} lines > {MAX_DIFF_LINES} limit).")
    print("Skipping Claude Code analysis. Consider splitting the PR.")
    # Optional: post a comment to the PR explaining
    sys.exit(0)  # exit 0 so the workflow isn't marked as failed

Trade-off: large PRs are the ones that would benefit most from automatic analysis, but also the ones that cost the most. The policy depends on the team: block ($0 spent, team pressured to split the PR), or run with chunking (more expensive but more useful — Module 2 capsule 06).


Rate Limiting: Protecting Against Extreme Cases

Imagine: someone force-pushes to a branch with 50 commits and triggers the workflow 50 times. Or a misconfigured bot opens 100 PRs in a day.

Without protection, those cases can generate bills of hundreds of dollars.

Concurrency: only one run per PR

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  cancel-in-progress: true

jobs:
  analyze:
    # ...

How it works: if a new run arrives for the same PR while one is in progress, the old one is canceled and only the new one runs. It saves duplicate runs when a developer pushes 3 times in a row.

Monthly quota with a variable secret

For stricter cases, you can keep a manual count and abort if it exceeds:

- name: Check monthly quota
  run: |
    USAGE_FILE="usage_$(date +%Y%m).txt"
    if [ -f "$USAGE_FILE" ]; then
      CURRENT=$(cat "$USAGE_FILE")
    else
      CURRENT=0
    fi
    if [ "$CURRENT" -gt 1000 ]; then
      echo "Monthly run quota exceeded ($CURRENT runs)."
      exit 0
    fi
    echo $((CURRENT + 1)) > "$USAGE_FILE"

(This is illustrative; in production we'd use a more robust tracking system, not files on the runner.)

GitHub Actions Spending Limit

GitHub lets you configure a spending limit at the organization or account level:

Settings → Billing → Plans and usage → Set spending limit

If you reach the limit, the workflows pause automatically. It's the last line of defense for private repos.


Optimizing with Cache

Some costs can be reduced with caching:

Python dependency cache

- name: Setup Python
  uses: actions/setup-python@v5
  with:
    python-version: '3.11'
    cache: 'pip'  # ← automatic pip cache

Reduces the pip install time from 30s to 3s. With 600 runs/month, it saves ~5 hours of runner = ~$2.5/month.

Custom cache for data

If your script downloads something that doesn't change between PRs (e.g. a pattern database, a CVE list), cache it:

- name: Cache vulnerability database
  uses: actions/cache@v4
  with:
    path: ~/.cache/vuln-db
    key: vuln-db-${{ hashFiles('.github/db-version.txt') }}

Common Cost Pitfalls

Error 1: Using Sonnet or Opus by default

Symptom: Monthly bill 5-10× higher than expected.

Why it happens: "Better model = better result". Not always. For general PR analysis, Haiku is enough.

How to fix it: Start with Haiku. Move up to Sonnet only if Haiku's result is notably insufficient. Opus rarely justifies itself for CI.

Error 2: Not configuring concurrency

Symptom: PRs with many pushes generate duplicate runs that execute in parallel, all paying.

Why it happens: By default, GitHub runs each trigger in parallel. With 5 pushes in 1 minute, that's 5 simultaneous runs.

How to fix it: concurrency with cancel-in-progress: true. Only the last run completes, the previous ones are canceled.

Error 3: Ignoring the runner cost

Symptom: The team only counts API tokens. The GitHub Actions bill surprises them.

Why it happens: The runner costs $0.008/min for private repos. With workflows of 5+ minutes, it adds up fast.

How to fix it: Optimize the total time: pip cache, avoid unnecessary steps, parallelism where applicable.

Error 4: Not filtering bots

Symptom: The workflow bot analyzes the Dependabot bot's PRs. They generate valueless analysis.

Why it happens: There's no if: github.actor != 'dependabot[bot]' filter.

How to fix it: Filter bots explicitly. Typical saving: 20-30% of runs (depends on the repo's level of automated updates).

Error 5: No spending limit

Symptom: A bad day (a bug in the workflow that loops, an attack from a malicious contributor) generates a $1000 bill.

Why it happens: Without a spending limit in GitHub Billing, the cost can grow unlimited.

How to fix it: Configure a reasonable spending limit in Settings → Billing. It's the safety net.


Diagnosis: Is Your Workflow Optimized?

Question 1: Do you know how much last month of Claude Code in CI cost you?

If you said "I don't know", open Anthropic console → Billing and look. Without that figure, you can't optimize.

Question 2: Does your workflow have path filters?

If not, you're probably running on docs/config changes that don't benefit. An easy saving of 30-50% of runs.

Question 3: Does your workflow have `concurrency` configured?

If not, parallel runs on multiple pushes waste runs. Enabling it is 1 line.

Question 4: Do you filter automatic bot PRs (Dependabot, Renovate)?

If not, you're analyzing version-update PRs that almost never add useful analysis.

Question 5: Do you have a spending limit configured in GitHub Billing and an alerts system in the Anthropic console?

Without these two safety nets, a silent incident can generate $1000+ before you notice.


Exercises

Exercise 1: Calculate the estimated monthly cost (Easy)

For your team, calculate: developers × PRs/week × updates/PR × 4 weeks × $0.017. Compare it with your real bill (if you have it). Identify whether there's a deviation.

Exercise 2: Implement path filters (Medium)

Configure paths-ignore in your workflow so it doesn't run when the PR only modifies docs (**.md, docs/**). Verify with a docs PR that the workflow doesn't trigger.

Exercise 3: Configure concurrency + bot skipping (Medium)

Add concurrency to cancel duplicate runs, and if to skip bot PRs. Verify with two pushes in a row to the same PR (it should cancel the first) and with a PR generated by Dependabot (it should skip).

See integrated solution
name: Claude Code Analysis

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
  cancel-in-progress: true

on:
  pull_request:
    types: [opened, synchronize, ready_for_review]
    paths:
      - 'src/**'
      - 'tests/**'
      - 'package.json'
    paths-ignore:
      - '**.md'

jobs:
  analyze:
    if: |
      github.actor != 'dependabot[bot]' &&
      github.actor != 'renovate[bot]' &&
      github.event.pull_request.draft == false
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      contents: read
    steps:
      # ... rest of the workflow

The four combined optimizations (paths, concurrency, bots, draft) typically reduce runs by 50-70%.

Exercise 4: Configure a spending limit (Easy)

In GitHub Billing, configure a spending limit. In the Anthropic console, configure a budget alert. Document it in the repo's SECURITY.md or equivalent.


Summary

  • Cost per run = Anthropic API + GitHub Actions runner
  • The model is the #1 cost factor — Haiku for general analysis, Sonnet/Opus only if justified
  • Path filters save 30-50% of runs in repos with active docs
  • if conditions skip bots, drafts, and commits with [skip ai]
  • concurrency avoids duplicate runs from consecutive pushes
  • A spending limit in GitHub Billing is the last line of defense
  • Pip and custom cache reduce runner minutes

Next capsule: Module 2 — Automated Code Review on PRs. So far the workflow runs a general analysis; Module 2 specializes it in code review with inline comments. You take everything learned in this module (workflows, secrets, output, costs) to the next level.


Additional Resources

  1. Anthropic Pricing — Current prices per model
  2. GitHub Actions Pricing — Runner costs
  3. GitHub Actions: paths filters — Official documentation
  4. GitHub Actions: concurrency — Official documentation
  5. GitHub Actions: caching — Dependency cache
  6. Anthropic Token Counting — Estimate tokens before the call
  7. GitHub Actions: usage limits — Platform limits