Automatizar la API de GitHub con Claude Code de forma segura
Guía práctica para automatizar GitHub API con Claude Code: permisos, paginación, límites, webhooks y ejemplos Node.
La API de GitHub permite leer y actualizar issues, pull requests, releases, metadatos de repositorios, workflows y webhooks desde código. Con Claude Code, esto puede convertirse en una automatización revisable: clasificar issues, detectar PRs abandonados, preparar notas de release y generar un informe diario de salud del repositorio.
El riesgo es que una automatización insegura también se crea rápido. Los errores más comunes son registrar un token en logs, usar un classic token con permisos demasiado amplios, leer solo la primera página de resultados, entrar en bucles de rate limit, aceptar webhooks sin verificar firma o permitir cambios masivos destructivos. La meta no es tener “un script que funciona”, sino un flujo que no filtre secretos, use permisos mínimos y falle de forma comprensible.
Trabaja siempre con documentación oficial cerca: GitHub REST API docs, rate limits de REST API, validación de webhooks y GitHub GraphQL API. Para conectarlo con el flujo del equipo, revisa también la guía de Git workflow y la guía avanzada de GitHub Actions.
Arquitectura segura
Empieza con lectura. Un script que lista issues o PRs permite validar permisos, paginación y formato de salida sin tocar datos. Después agrega dry-run: el programa muestra qué labels, comentarios o cambios aplicaría, pero no los ejecuta. Solo al final se permite escribir, con un flag explícito como APPLY=true y un límite de elementos.
flowchart LR
A["Definir objetivo y límites para Claude Code"] --> B["Leer con permisos mínimos"]
B --> C["Resolver paginación y rate limits"]
C --> D["Mostrar dry-run"]
D --> E["Aplicar con aprobación explícita"]
E --> F["Operar con schedule o Webhook"]
REST y GraphQL tienen usos distintos. REST es la mejor primera opción para acciones directas: listar PRs, añadir un label, crear un release o leer el estado de un workflow. Sus endpoints son fáciles de probar y Claude Code puede implementarlos uno por uno. GraphQL brilla cuando necesitas un reporte con datos relacionados: repositorio, PR, autor, reviews, labels y milestones en una sola consulta.
| Criterio | REST API | GraphQL API |
|---|---|---|
| Ideal para | Acciones sobre un recurso concreto | Reportes y dashboards cruzados |
| Dificultad | Baja, basada en URL y método HTTP | Media, requiere diseñar query |
| Cómo pedirlo a Claude Code | Implementar endpoint por endpoint | Definir campos y esquema primero |
| Riesgo principal | Olvidar paginación o permisos | Query demasiado grande o costosa |
Tokens y permisos mínimos
Un token de GitHub debe tratarse como una contraseña. No lo pegues en código, ejemplos, capturas, snapshots de test ni logs. Los ejemplos de este artículo leen GITHUB_TOKEN desde variables de entorno y nunca imprimen su valor.
Siempre que puedas, usa fine-grained personal access tokens. Limita el token al repositorio y a los permisos necesarios. Un bot de triage puede necesitar Issues: Read and write; un reporte de PRs antiguos normalmente solo necesita Pull requests: Read-only; un generador de notas de release puede empezar con Contents: Read y Metadata: Read. El scope repo de un classic token es cómodo, pero demasiado amplio para una automatización diaria.
En GitHub Actions también debes declarar permissions. Un informe diario no necesita write access. Un job que añade labels debe tener solo el permiso de escritura correspondiente. Antes de pedir a Claude Code que escriba YAML, pídele una tabla con función, endpoint y permiso requerido.
Crea un script Node.js que use GitHub REST API.
Requisitos:
- Leer el token desde process.env.GITHUB_TOKEN.
- No imprimir el token ni headers Authorization completos.
- Leer owner/repo desde variables de entorno.
- La primera versión debe ser solo lectura.
- Usar fetch y manejar status code, paginación y rate-limit headers.
- Incluir una sección README con los permisos necesarios.
Script de solo lectura
Este ejemplo funciona con Node.js 18 o superior. Lista issues abiertos sin modificar el repositorio.
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);
El siguiente paso seguro no es cerrar issues. Es imprimir candidatos: labels sugeridos, comentarios propuestos o responsables posibles. Para escribir de verdad, exige un flag explícito y un límite de cambios.
Paginación y rate limits
Muchos endpoints de listado están paginados. per_page=100 no significa “todos los elementos”; significa una página de hasta 100. Si un generador de release notes lee solo la primera página, omitirá PRs antiguos. Si un reporte de PRs obsoletos hace lo mismo, dará una falsa sensación de orden.
Los rate limits también deben diseñarse desde el principio. Reintentar inmediatamente en cada 403 o 429 solo consume CI y cuota. Lee retry-after y x-ratelimit-reset, espera con un techo razonable y falla después de un número fijo de intentos.
// 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 })));
}
Después de crear un helper común, pide a Claude Code que busque llamadas directas a api.github.com y las pase por esta función. Así evitas que una nueva automatización vuelva a leer solo una página.
Webhooks: firma e idempotencia
Los webhooks sirven cuando quieres reaccionar a eventos en vez de consultar cada cierto tiempo. Un PR abierto puede producir una sugerencia de label; un issue nuevo puede entrar en una cola de triage; un release publicado puede activar una notificación. Como el endpoint suele estar expuesto, debes verificar x-hub-signature-256 antes de confiar en el body.
También necesitas idempotencia: si GitHub reenvía la misma entrega, el sistema no debe duplicar comentarios ni labels. Guarda x-github-delivery antes de ejecutar efectos secundarios. El ejemplo usa un Set en memoria solo para mostrar la forma; en producción usa base de datos o Redis.
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.");
});
Al ampliar este servidor con Claude Code, dile que no procese JSON antes de verificar la firma, que no haga cambios destructivos dentro de la petición HTTP y que ignore entregas repetidas.
Casos de uso concretos
Un issue triage bot revisa nuevos issues, detecta si faltan pasos de reproducción y sugiere labels como bug, question o needs-repro. Primero debe producir un reporte, no comentar automáticamente.
Un stale PR reporter encuentra PRs sin cambios durante 30 días, con review pendiente o CI fallido. La versión segura informa; no cierra PRs. El cierre debe ser otro comando con dry-run y límite.
Un release note generator agrupa PRs mergeados entre dos tags por label. GraphQL ayuda si necesitas autores, reviewers y milestones juntos, pero REST basta para una primera versión. Mantén el resultado como borrador.
Un daily repository health report combina issues abiertos, PRs viejos, workflows fallidos, alertas Dependabot, releases recientes y backlog de reviews. El reporte debe señalar las tres acciones más importantes del día. Este flujo conecta bien con Claude Code workflow automation y review workflow checklist.
Fallos que debes evitar
El primer fallo es filtrar un classic token en logs. Evita console.log(process.env), dumps completos de request y debug logs con headers. Revisa con rg "GITHUB_TOKEN|Authorization|process.env".
El segundo es usar permisos demasiado amplios. Un reporte de solo lectura no necesita write access. El tercero es ignorar la paginación. El cuarto es crear un loop de rate limit. El quinto es aceptar webhooks sin firma. El sexto es permitir cambios masivos destructivos sin dry-run, límite de cantidad, audit log y plan de rollback.
Rol de Claude Code
Claude Code debe construir el scaffolding: cliente API, helper de paginación, tests, README, workflow de GitHub Actions y checklist de revisión. Las decisiones sobre tokens de producción, permisos y cambios destructivos deben quedarse en manos humanas. Escribe estas reglas en CLAUDE.md para que el equipo use el mismo estándar.
ClaudeCodeLab ayuda a equipos a definir permisos, CLAUDE.md, review gates, workflows y operación de webhooks. Para adopción en equipo, empieza por formación y consultoría Claude Code. Para práctica individual, revisa la chuleta gratuita y los templates.
Resultado
GitHub API y Claude Code reducen mucho el mantenimiento repetitivo si respetas los límites: REST para acciones directas, GraphQL para reportes complejos, permisos mínimos, paginación real, rate limits controlados, webhooks verificados e idempotencia.
Resultado práctico de Masa: los scripts de lectura, paginación y verificación de firma de este artículo quedaron en una forma copiable y revisable. En un repositorio real, empezar con un reporte diario antes de permitir escritura generó más confianza que un bot que cerraba issues desde el primer día.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Workflow de Obsidian a CLAUDE.md con Claude Code
Convierte notas de trabajo de Obsidian en notas operativas de CLAUDE.md para no repetir contexto.
Claude Code Revenue CTA Routing: de artículos a PDF, Gumroad y consulta
Un flujo con Claude Code para dirigir lectores a PDF gratis, Gumroad o consulta según intención.
Reglas de handoff para equipos con Claude Code: evidencia, permisos, rollback e ingresos
Formato práctico para entregar trabajo de Claude Code con pruebas, permisos, rollback, PDF gratis, Gumroad y consulta.