Automatize a GitHub API com Claude Code sem vazar tokens
Guia prático para automatizar a GitHub API com Claude Code: permissões, paginação, limites, webhooks e exemplos Node.
A GitHub API permite ler e atualizar issues, pull requests, releases, metadados de repositório, workflows e webhooks por código. Com Claude Code, ela ajuda a transformar manutenção repetitiva em fluxos revisáveis: triagem de novas issues, relatório de PRs parados, rascunho de release notes e daily repository health report.
O problema é que uma automação perigosa também nasce rápido. Os erros mais comuns são gravar token em log, usar classic token com scope amplo demais, ler só a primeira página, entrar em loop quando há rate limit, aceitar webhook sem validar assinatura e fazer edições destrutivas em massa. O pedido para Claude Code precisa falar de segurança, não apenas de funcionalidade.
Use as fontes oficiais como base: GitHub REST API docs, REST API rate limits, validação de webhooks e GitHub GraphQL API. Para encaixar isso no fluxo do time, leia também o guia de Git workflow e o guia avançado de GitHub Actions.
Modelo seguro
Comece com leitura. Um script que lista issues ou PRs permite confirmar permissões, paginação e formato de saída sem alterar nada. Depois adicione dry-run: o programa mostra labels, comentários ou mudanças propostas, mas não aplica. Só então crie escrita real, protegida por um flag explícito como APPLY=true e por um limite de itens.
flowchart LR
A["Definir objetivo e limites para Claude Code"] --> B["Ler com permissões mínimas"]
B --> C["Tratar paginação e rate limits"]
C --> D["Mostrar dry-run"]
D --> E["Aplicar com aprovação explícita"]
E --> F["Operar com schedule ou Webhook"]
REST e GraphQL têm papéis diferentes. REST é melhor para começar porque cada endpoint representa uma ação clara: listar PRs, adicionar label, criar release, ler workflow run. Claude Code consegue implementar isso em passos pequenos. GraphQL vale quando o relatório precisa reunir repository, PR, autor, reviews, labels e milestones em uma consulta.
| Critério | REST API | GraphQL API |
|---|---|---|
| Melhor uso | Ação direta em um recurso | Relatórios com dados relacionados |
| Dificuldade | Menor, URL e método HTTP | Maior, query e schema |
| Pedido para Claude Code | Endpoint por endpoint | Definir campos primeiro |
| Risco principal | Esquecer paginação ou permissão | Query grande ou cara demais |
Tokens e permissões
Token do GitHub deve ser tratado como senha. Não cole no código, em README, prints, snapshots de teste ou logs. Os exemplos abaixo leem GITHUB_TOKEN do ambiente e nunca imprimem o valor.
Prefira fine-grained personal access tokens e limite repositório e permissões. Um bot de triagem pode precisar de Issues: Read and write; um stale PR reporter normalmente precisa apenas de Pull requests: Read-only; um gerador de release notes pode começar com Contents: Read e Metadata: Read. O scope repo de um classic token é prático, mas amplo demais para uso recorrente.
Em GitHub Actions, declare permissions. Um relatório diário não precisa de write access. Um job que adiciona labels deve ter só a permissão de escrita necessária. Antes de pedir o YAML a Claude Code, peça uma tabela com função, endpoint e permissão.
Crie um script Node.js que use a GitHub REST API.
Requisitos:
- Ler token de process.env.GITHUB_TOKEN.
- Não imprimir token nem Authorization headers completos.
- Ler owner/repo de variáveis de ambiente.
- Primeira versão somente leitura.
- Usar fetch e tratar status code, pagination e rate-limit headers.
- Incluir seção README curta com permissões necessárias.
Script somente leitura
Este exemplo funciona em Node.js 18 ou superior e lista issues abertas sem modificar o repositório.
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);
O próximo passo seguro é mostrar propostas, não fechar issues. Peça para o script imprimir labels sugeridas, comentários ou responsáveis, e só escrever quando um flag explícito estiver presente.
Paginação e rate limits
Muitos endpoints de lista usam pagination. per_page=100 não significa todos os itens; significa uma página de até 100. Um gerador de release notes que lê só a primeira página perde PRs antigos. Um relatório de PRs parados pode parecer bom mesmo ignorando grande parte dos dados.
Rate limit também precisa de desenho. Tentar de novo imediatamente em todo 403 ou 429 gasta CI e quota. Leia retry-after e x-ratelimit-reset, espere dentro de um limite razoável e falhe depois de algumas tentativas.
// 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 })));
}
Depois de criar esse helper, peça a Claude Code para buscar api.github.com e substituir chamadas diretas. Assim a paginação fica consistente.
Webhooks com assinatura e idempotência
Webhooks são úteis quando a automação deve reagir a eventos. Um PR aberto pode gerar sugestão de label; uma issue nova pode entrar em uma fila de triagem; uma release publicada pode notificar outro sistema. Como o endpoint fica exposto, valide x-hub-signature-256 antes de confiar no payload.
Também cuide de idempotência: se a mesma delivery chegar duas vezes, o sistema não deve duplicar comentários ou labels. Grave x-github-delivery antes de efeitos colaterais. O Set abaixo é apenas demonstração; em produção use Redis ou banco.
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.");
});
Ao expandir esse servidor, diga a Claude Code para não processar JSON antes da verificação, não executar mudanças destrutivas dentro da requisição HTTP e ignorar delivery já vista.
Quatro casos de uso
Um issue triage bot verifica se novas issues têm passos de reprodução, ambiente e labels possíveis como bug, question ou needs-repro. A primeira versão deve gerar relatório, não comentário automático.
Um stale PR reporter lista PRs sem atualização por 30 dias, com review pendente ou CI quebrado. A versão segura informa; não fecha PRs.
Um release note generator coleta PRs mergeados entre duas tags e agrupa por label. REST basta no início; GraphQL ajuda quando autores, reviewers e milestones entram no relatório.
Um daily repository health report combina issues abertas, PRs antigos, workflows falhando, alertas Dependabot, releases recentes e backlog de review. O relatório deve destacar as três ações mais importantes do dia. Esse fluxo combina com Claude Code workflow automation e review workflow checklist.
Erros comuns
O primeiro erro é vazar classic token em log. Evite console.log(process.env), dumps de request e debug logs com headers. Revise com rg "GITHUB_TOKEN|Authorization|process.env".
O segundo erro é permissão ampla demais. Relatório somente leitura não precisa escrever. O terceiro é esquecer paginação. O quarto é loop de rate limit. O quinto é webhook sem assinatura validada. O sexto é bulk edit destrutivo sem dry-run, limite, audit log e plano de rollback.
Papel do Claude Code
Claude Code deve criar cliente API, helpers, testes, README, GitHub Actions schedule e checklist de revisão. Token de produção, aprovação de permissões e mudanças destrutivas ficam com humanos. Registre essas regras em CLAUDE.md.
ClaudeCodeLab ajuda times a definir CLAUDE.md, permissões, GitHub Actions, review gates e operação de Webhook. Para adoção em equipe, comece por treinamento e consultoria Claude Code. Para prática individual, use a cheatsheet gratuita e os templates.
Resultado
GitHub API com Claude Code reduz manutenção repetitiva quando os limites são claros: REST para ações diretas, GraphQL para relatórios complexos, permissões mínimas, paginação real, rate limits controlados, Webhooks verificados e processamento idempotente.
Resultado prático de Masa: os exemplos de leitura, paginação e validação de assinatura ficaram em um formato copiável e fácil de revisar. Em repositórios reais, começar com daily report antes de permitir escrita trouxe mais confiança do que um bot fechando issues no primeiro dia.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.