← Blog

Claude API GitHub Actions CI/CD Setup Guide

2026-10-21 · 5 min read · SubToAPI Team

Setting up the Claude API inside a GitHub Actions workflow means storing your API key as a repository secret, calling the API from a workflow step (via curl or a small script), and wiring the output into your pipeline — as a PR comment, a commit check, or a generated artifact. The whole setup takes under 30 minutes and doesn't require any extra infrastructure beyond what GitHub Actions already gives you.

This guide walks through a complete working example: a workflow that runs Claude on every pull request to review the diff and post feedback as a comment. You can adapt the same pattern for test generation, changelog writing, or documentation checks.

Why use Claude in CI/CD

Running an LLM as part of your pipeline is useful for tasks that are repetitive but need judgment:

None of these need a long-running service. A GitHub Actions job spins up, calls the API once or twice, and exits — which is exactly the kind of workload that's cheap and simple to automate.

Step 1: Store your API key as a secret

Never put API keys directly in workflow files. Add the key under your repo's Settings → Secrets and variables → Actions:

ANTHROPIC_API_KEY=sk-ant-...

If you're using SubToAPI instead of a raw Anthropic key, the setup is identical — you just generate an application key (sub_live_...) from your dashboard and store that as the secret instead. This is a common choice for teams who already pay for Claude access through a Pro/Team/Max subscription and want to reuse it across CI jobs without managing separate Anthropic billing. See /docs/quickstart for how keys are issued.

Step 2: Write the workflow file

Create .github/workflows/claude-review.yml:

name: Claude PR Review

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Get PR diff
        id: diff
        run: |
          git diff origin/${{ github.base_ref }}...HEAD > diff.txt
          echo "lines=$(wc -l < diff.txt)" >> $GITHUB_OUTPUT

      - name: Call Claude API
        env:
          API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          node .github/scripts/review.js

      - name: Post comment
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const body = fs.readFileSync('review.md', 'utf8');
            await github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body,
            });

Step 3: The review script

// .github/scripts/review.js
const fs = require('fs');

async function main() {
  const diff = fs.readFileSync('diff.txt', 'utf8').slice(0, 15000);

  const response = await fetch('https://api.subtoapi.app/v1/messages', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'claude-sonnet-4-5',
      max_tokens: 1024,
      messages: [{
        role: 'user',
        content: `Review this diff for bugs, missing error handling, and style issues. Be concise, use bullet points, and only flag real problems:\n\n${diff}`,
      }],
    }),
  });

  const data = await response.json();
  const text = data.content[0].text;
  fs.writeFileSync('review.md', `## Claude Review\n\n${text}`);
}

main();

This uses the standard /v1/messages endpoint format — see /docs/messages for the full request and response schema, including how to handle longer diffs with pagination or truncation.

Handling diffs that are too large

PRs with large diffs will exceed context limits or just produce unfocused reviews. Two practical fixes:

const changedFiles = diff.split(/^diff --git/m).filter(Boolean);
for (const chunk of changedFiles) {
  // call API per chunk, accumulate results
}

Adding a required status check

If you want Claude's review to block merges on serious issues (not just comment), parse the response for a severity marker and set the job's exit code accordingly:

if (text.includes('BLOCKING')) {
  process.exit(1);
}

Then mark the review job as a required check in your branch protection rules. Be conservative here — treat this as a safety net for obvious issues, not a replacement for human review.

Rate limits and parallel PRs

If your team opens many PRs at once, concurrent workflow runs can hit rate limits. Two mitigations:

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

Using streaming for long-running jobs

For workflows generating longer outputs — full test suites, long changelogs — streaming avoids timeout issues on slow connections and lets you write partial output incrementally. See /docs/streaming for the event format if you want to adapt the script above to stream to a log file as it generates.

Setting it up with SubToAPI

If your organization already has a Claude subscription and doesn't want to provision a separate Anthropic API account for CI, SubToAPI turns that subscription into an application-ready key you can drop straight into the ANTHROPIC_API_KEY secret above — no code changes needed beyond pointing the base URL at api.subtoapi.app. Start with a free trial at /signup, or compare Solo, Team, and Scale plans at /pricing if multiple repos or team members need their own keys for usage tracking.

questions

Do I need a paid GitHub Actions plan to run Claude API calls in CI? No — public repos get free Actions minutes, and private repos get a generous free tier too. The API call itself is billed separately through your Claude or SubToAPI plan, not through GitHub.

Can I use this setup with GitLab CI or other pipelines instead of GitHub Actions? Yes. The pattern is identical — store the key as a pipeline secret/variable and call the /v1/messages endpoint from a job step. Only the YAML syntax for secrets and job definitions changes.

What's the safest way to prevent leaking the API key in logs? Never echo the key or print full request headers in a step. GitHub Actions automatically masks secret values in logs, but double-check by keeping the key out of run: command strings and passing it only through env:.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →