GitHub API mit Claude Code sicher automatisieren
Praxisguide für GitHub API mit Claude Code: Rechte, Pagination, Rate Limits, Webhooks und lauffähige Node-Beispiele.
Die GitHub API macht Issues, Pull Requests, Releases, Repository-Metadaten, Workflow-Status und Webhooks per Code zugänglich. Mit Claude Code lassen sich daraus schnell wiederholbare Abläufe bauen: neue Issues vorsortieren, veraltete Pull Requests melden, Release Notes vorbereiten oder jeden Morgen einen Repository-Health-Report erzeugen.
Genau diese Geschwindigkeit kann aber auch schaden. Typische Fehler sind Tokens in Logs, classic tokens mit zu breitem repo-Scope, fehlende Pagination, endlose Retry-Schleifen bei Rate Limits, nicht geprüfte Webhook-Signaturen und destruktive Massenänderungen ohne Dry Run. Die Aufgabe für Claude Code sollte deshalb nicht “schreib ein Skript” heißen, sondern “schreib eine sichere, nachvollziehbare Automatisierung”.
Prüfe die Details immer gegen die offiziellen Quellen: GitHub REST API docs, REST API rate limits, Webhook delivery validation und GitHub GraphQL API. Für den Teamkontext passen dazu der Git workflow guide und der GitHub Actions advanced guide.
Sicherer Grundaufbau
Beginne mit einem rein lesenden Skript. Es listet die betroffenen Issues oder PRs, zeigt das Format und beweist, dass Token und Rechte passen. Danach kommt ein Dry Run: Das Programm zeigt geplante Labels, Kommentare oder Zuordnungen, ändert aber nichts. Erst danach sollte ein Schreibpfad entstehen, geschützt durch ein explizites Flag wie APPLY=true und eine maximale Anzahl an Änderungen.
flowchart LR
A["Ziel und Grenzen an Claude Code geben"] --> B["Mit Minimalrechten lesen"]
B --> C["Pagination und Rate Limits behandeln"]
C --> D["Dry Run anzeigen"]
D --> E["Mit expliziter Freigabe schreiben"]
E --> F["Per Schedule oder Webhook betreiben"]
REST und GraphQL haben unterschiedliche Stärken. REST ist der einfachste Einstieg für konkrete Ressourcen: PRs listen, ein Label setzen, eine Release anlegen oder einen Workflow-Run lesen. Die Endpoints sind sichtbar und gut in kleinen Schritten mit Claude Code umzusetzen. GraphQL lohnt sich, wenn ein Report viele zusammenhängende Felder braucht, etwa Repository, PR, Autor, Reviews, Labels und Milestones in einer Abfrage.
| Kriterium | REST API | GraphQL API |
|---|---|---|
| Beste Nutzung | Einzelne Ressourcen bearbeiten oder lesen | Reports über mehrere Ressourcen |
| Lernkurve | Niedrig, URL und HTTP-Methode | Höher, Query und Schema |
| Prompt an Claude Code | Endpoint für Endpoint | Erst benötigte Felder definieren |
| Hauptgefahr | Pagination oder Rechte vergessen | Query wird zu groß oder teuer |
Tokens und Berechtigungen
Ein GitHub-Token ist ein Passwort. Schreibe es nicht in Code, README-Beispiele, Screenshots, Test-Snapshots oder Logs. Die Beispiele hier lesen GITHUB_TOKEN aus der Umgebung und geben den Wert nie aus.
Nutze möglichst fine-grained personal access tokens. Begrenze Token auf Repository und nötige Permissions. Ein Issue-Triage-Bot braucht eventuell Issues: Read and write; ein Stale-PR-Reporter kommt meist mit Pull requests: Read-only aus; Release-Note-Entwürfe starten oft mit Contents: Read und Metadata: Read. Der repo-Scope eines classic token ist bequem, aber für dauerhafte Automatisierung zu breit.
In GitHub Actions gehört permissions explizit in die Workflow-Datei. Ein täglicher Report braucht keine Schreibrechte. Ein Label-Job bekommt nur die Schreibrechte, die er wirklich nutzt. Bitte Claude Code vor dem YAML um eine Tabelle mit Funktion, Endpoint und benötigter Permission.
Erstelle ein Node.js-Skript für die GitHub REST API.
Anforderungen:
- Token aus process.env.GITHUB_TOKEN lesen.
- Token und Authorization-Header nie ausgeben.
- owner/repo aus Umgebungsvariablen lesen.
- Erste Version nur lesend, keine Änderungen an Issues, PRs oder Releases.
- fetch nutzen und Statuscodes, Pagination und Rate-Limit-Header behandeln.
- Kurze README-Sektion mit nötigen GitHub-Rechten schreiben.
Lauffähiges Leseskript
Dieses Beispiel läuft mit Node.js 18 oder neuer und listet offene Issues. Es schreibt nichts ins Repository.
export GITHUB_TOKEN="github_pat_xxx"
export GITHUB_OWNER="octocat"
export GITHUB_REPO="Hello-World"
node scripts/list-open-issues.mjs
// scripts/list-open-issues.mjs
const { GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO } = process.env;
if (!GITHUB_TOKEN || !GITHUB_OWNER || !GITHUB_REPO) {
throw new Error("Set GITHUB_TOKEN, GITHUB_OWNER, and GITHUB_REPO.");
}
const apiVersion = "2026-03-10";
async function github(path, options = {}) {
const response = await fetch(`https://api.github.com${path}`, {
...options,
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${GITHUB_TOKEN}`,
"X-GitHub-Api-Version": apiVersion,
"User-Agent": "claudecodelab-safe-github-api-example",
...(options.headers ?? {}),
},
});
if (!response.ok) {
const body = await response.text();
throw new Error(`GitHub API ${response.status}: ${body.slice(0, 500)}`);
}
return response.json();
}
const issues = await github(
`/repos/${encodeURIComponent(GITHUB_OWNER)}/${encodeURIComponent(GITHUB_REPO)}/issues?state=open&per_page=10`,
);
const rows = issues
.filter((issue) => !issue.pull_request)
.map((issue) => ({
number: issue.number,
title: issue.title,
labels: issue.labels.map((label) => label.name).join(", "),
updated: issue.updated_at,
}));
console.table(rows);
Der sichere nächste Schritt ist kein automatisches Schließen. Lass das Skript erst Vorschläge ausgeben: Label-Kandidaten, Kommentarentwürfe oder Assignees. Schreiben darf es erst mit explizitem Flag und Mengengrenze.
Pagination und Rate Limits
Viele Listen-Endpunkte sind paginiert. per_page=100 bedeutet nur eine Seite mit bis zu 100 Einträgen. Ein Release-Note-Generator, der nur Seite eins liest, vergisst ältere gemergte PRs. Ein Stale-PR-Report kann dadurch falsch sauber aussehen.
Rate Limits brauchen ebenfalls Logik. Sofortiges Wiederholen bei jedem 403 oder 429 verbraucht CI-Zeit und API-Quota. Lies retry-after und x-ratelimit-reset, warte nur in einem vernünftigen Rahmen und brich nach einer festen Zahl von Versuchen ab.
// scripts/github-pages.mjs
const token = process.env.GITHUB_TOKEN;
if (!token) throw new Error("Set GITHUB_TOKEN.");
const apiBase = "https://api.github.com";
const apiVersion = "2026-03-10";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function defaultHeaders() {
return {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${token}`,
"X-GitHub-Api-Version": apiVersion,
"User-Agent": "claudecodelab-pagination-example",
};
}
function parseNextLink(linkHeader) {
if (!linkHeader) return null;
for (const part of linkHeader.split(",")) {
const match = part.match(/<([^>]+)>;\s*rel="([^"]+)"/);
if (match && match[2] === "next") return match[1];
}
return null;
}
async function githubRequest(url, options = {}, attempt = 0) {
const response = await fetch(url, {
...options,
headers: {
...defaultHeaders(),
...(options.headers ?? {}),
},
});
if ((response.status === 403 || response.status === 429) && attempt < 2) {
const retryAfterSeconds = Number(response.headers.get("retry-after") ?? "0");
const resetSeconds = Number(response.headers.get("x-ratelimit-reset") ?? "0");
const resetDelayMs = resetSeconds > 0 ? resetSeconds * 1000 - Date.now() : 0;
const waitMs = Math.max(retryAfterSeconds * 1000, resetDelayMs, 0);
if (waitMs > 0 && waitMs <= 10 * 60 * 1000) {
await sleep(waitMs + 1000);
return githubRequest(url, options, attempt + 1);
}
}
if (!response.ok) {
const body = await response.text();
throw new Error(`GitHub API ${response.status}: ${body.slice(0, 500)}`);
}
return {
data: await response.json(),
nextUrl: parseNextLink(response.headers.get("link")),
};
}
export async function paginate(path) {
const items = [];
let url = path.startsWith("http") ? path : `${apiBase}${path}`;
while (url) {
const page = await githubRequest(url);
if (!Array.isArray(page.data)) {
throw new Error("paginate() expected an array response.");
}
items.push(...page.data);
url = page.nextUrl;
}
return items;
}
if (import.meta.url === `file://${process.argv[1]}`) {
const owner = process.env.GITHUB_OWNER;
const repo = process.env.GITHUB_REPO;
if (!owner || !repo) throw new Error("Set GITHUB_OWNER and GITHUB_REPO.");
const pulls = await paginate(
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls?state=open&per_page=100`,
);
console.table(pulls.map((pr) => ({ number: pr.number, title: pr.title, updated: pr.updated_at })));
}
Nach diesem Helper sollte Claude Code direkte api.github.com-Aufrufe suchen und ersetzen. So bleibt Pagination an einer Stelle kontrolliert.
Webhooks mit Signatur und Idempotenz
Webhooks eignen sich, wenn ein Ereignis statt Polling den Ablauf starten soll. Eine geöffnete PR kann Label-Vorschläge erzeugen, eine neue Issue in eine Triage-Queue gehen, eine Release eine Benachrichtigung auslösen. Weil der Endpoint öffentlich erreichbar ist, musst du x-hub-signature-256 prüfen, bevor du dem Payload vertraust.
Idempotenz bedeutet: dieselbe Delivery darf zweimal ankommen, ohne doppelte Kommentare oder Labels zu erzeugen. Speichere x-github-delivery, bevor Nebeneffekte passieren. Das Beispiel nutzt ein Set im Speicher; produktiv gehört das in Redis oder eine Datenbank.
npm install express
export GITHUB_WEBHOOK_SECRET="your-webhook-secret"
node webhook-server.mjs
// webhook-server.mjs
import crypto from "node:crypto";
import express from "express";
const secret = process.env.GITHUB_WEBHOOK_SECRET;
if (!secret) throw new Error("Set GITHUB_WEBHOOK_SECRET.");
const app = express();
const seenDeliveries = new Set();
function verifySignature(payloadBuffer, signatureHeader) {
if (!signatureHeader) return false;
const expected = `sha256=${crypto
.createHmac("sha256", secret)
.update(payloadBuffer)
.digest("hex")}`;
const actual = Buffer.from(signatureHeader, "utf8");
const expectedBuffer = Buffer.from(expected, "utf8");
return actual.length === expectedBuffer.length && crypto.timingSafeEqual(actual, expectedBuffer);
}
app.post("/github/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.get("x-hub-signature-256");
if (!verifySignature(req.body, signature)) {
return res.status(401).send("invalid signature");
}
const deliveryId = req.get("x-github-delivery");
if (!deliveryId) return res.status(400).send("missing delivery id");
if (seenDeliveries.has(deliveryId)) {
return res.status(202).send("duplicate ignored");
}
seenDeliveries.add(deliveryId);
const event = req.get("x-github-event");
const payload = JSON.parse(req.body.toString("utf8"));
console.log(
JSON.stringify({
event,
deliveryId,
repository: payload.repository?.full_name,
action: payload.action,
}),
);
return res.status(202).send("accepted");
});
app.listen(process.env.PORT ?? 3000, () => {
console.log("Listening for GitHub webhooks.");
});
Wenn Claude Code diesen Handler erweitert, gib klare Grenzen: kein JSON vor der Signaturprüfung verarbeiten, keine destruktiven Änderungen direkt im HTTP-Request, bekannte Delivery IDs ignorieren.
Vier konkrete Use Cases
Ein Issue-Triage-Bot prüft neue Issues auf fehlende Reproduktionsschritte und schlägt Labels wie bug, question oder needs-repro vor. Die erste Version schreibt nur einen Report.
Ein Stale-PR-Reporter findet PRs ohne Aktivität seit 30 Tagen, mit alter Review-Anfrage oder kaputter CI. Die sichere Variante berichtet nur und schließt nichts.
Ein Release-Note-Generator sammelt gemergte PRs zwischen zwei Tags und gruppiert sie nach Labels. REST reicht für den Start; GraphQL hilft bei Autoren, Reviews und Milestones.
Ein Daily Repository Health Report kombiniert offene Issues, alte PRs, fehlgeschlagene Workflows, Dependabot Alerts, neue Releases und Review-Rückstand. Er sollte die drei wichtigsten Aktionen des Tages zeigen. Dazu passen Claude Code workflow automation und die review workflow checklist.
Häufige Fehler
Der erste Fehler ist ein Token im Log. Vermeide console.log(process.env), Request-Dumps und CI-Debug-Logs mit Headern. Prüfe mit rg "GITHUB_TOKEN|Authorization|process.env".
Der zweite Fehler sind zu breite Rechte. Ein lesender Report braucht keinen Schreibzugriff. Der dritte ist fehlende Pagination. Der vierte ist eine Rate-Limit-Schleife. Der fünfte ist ein ungeprüfter Webhook. Der sechste sind destruktive Massenänderungen ohne Dry Run, Mengenlimit, Audit Log und Rollback-Plan.
Rolle von Claude Code
Claude Code sollte API-Client, Helper, Tests, README, GitHub-Actions-Zeitplan und Review-Checkliste bauen. Entscheidungen über Production Tokens, Rechte und destruktive Änderungen bleiben bei Menschen. Schreibe diese Regeln in CLAUDE.md.
ClaudeCodeLab unterstützt Teams bei CLAUDE.md, Rechtemodell, GitHub Actions, Review Gates und Webhook-Betrieb. Für Teams ist Claude Code Training und Beratung der passende Einstieg; Einzelpersonen können mit kostenlosem Cheatsheet und Templates starten.
Ergebnis
GitHub API und Claude Code sparen viel wiederholte Repository-Arbeit, wenn Grenzen klar sind: REST für direkte Aktionen, GraphQL für Reports, minimale Rechte, echte Pagination, kontrollierte Rate Limits, verifizierte Webhooks und idempotente Verarbeitung.
Masas Praxisergebnis: Das Leseskript, der Pagination-Helper und der Webhook-Server zur Signaturprüfung sind so aufgebaut, dass sie kopiert und geprüft werden können. In realen Repositories erzeugt ein täglicher Report vor dem ersten Schreibzugriff deutlich mehr Vertrauen als ein Bot, der sofort Issues schließt.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.