Spec-Driven Development + GitHub Actions + Claude Code
O problema que eu tinha em mãos
Toda esteira de desenvolvimento passa pelos mesmos gargalos manuais. Alguém escreve uma spec, alguém revisa, alguém implementa, alguém abre uma Pull Request (PR), alguém revisa de novo. Cada troca de mão é uma pausa, um contexto perdido, um "deixa eu ver isso amanhã".
No entanto, na squad em que atuo, decidimos atacar isso de um jeito diferente. Transformamos boa parte desse fluxo em uma esteira automatizada, em que um agente de IA (Claude Code) executa as etapas repetíveis do processo, e um humano só entra nos pontos que realmente exigem análise.
O resultado é uma pipeline rodando dentro do GitHub, dirigida inteiramente por labels em Issues, seguindo o modelo de Spec-Driven Development (Spec Kit).
E eu pensei: porque não falar sobre essa transformação? Assim, esse artigo é o "como" por trás disso, incluindo os erros que travaram o piloto e o que ainda estamos ajustando.
O conceito central
A regra de ouro do design é simples de enunciar e poderosa na prática: A label sdd:* do Issue é a fonte da verdade. O board do GitHub Project é apenas a visão.
Isso resolve um problema clássico de automação. Quando o estado "real" mora em um lugar editável (o board) por qualquer um, a qualquer momento, e a automação tenta inferir o que fazer a partir dele, criam-se condições de corrida e estados inconsistentes. Ao colocar a label como única fonte de verdade, o board vira só uma janela: bonita, útil, mas descartável.
O mapeamento fundamental é 1 card = 1 spec = 1 branch:
Card: um Issue aberto por um template específico ("Spec card").
Spec: a pasta specs/NNN-slug/ gerada pelo agente.
Branch: NNN-slug, nascida de staging, com PR de volta pra staging.
Mover o card de coluna é, na prática, trocar a label. Cada troca dispara os workflows via evento issues: labeled.
A máquina de estados
O ponto mais importante do design é que nem toda etapa é automática. Cada label carrega um type que decide o comportamento:
| Type | Significado | O agente executa? |
|---|---|---|
| gate | Etapas de validação humana | Não, só espera |
| auto | Etapa do agente | Sim, roda, commita, avança |
| external | Delegado a outro workflow (deploy) | Não |
| terminal | Fim (done) | Não |
| failure | Bloqueado | Não |
Na prática, o fluxo intercala etapas automáticas com etapas de validação humana. O agente gera a spec, mas um humano valida antes do plano ser gerado. O agente gera o plano e as tasks, mas um humano aprova antes da implementação rodar. O agente implementa, mas um humano revisa o PR antes do deploy.
Essa alternância responde direto a uma pergunta que qualquer engenheiro sênior faz antes de aprovar esse tipo de sistema: e se o agente decidir algo errado? Bem, a resposta é que ele nunca decide sozinho o suficiente para causar dano sem alguém ter validado o passo anterior.
Anatomia de uma execução automática
Quando uma label do tipo auto é aplicada, o workflow principal roda uma sequência bem definida:
- Resolve o estágio, lendo a configuração da label e exportando os atributos necessários.
- Garante que a branch do card existe, criando-a a partir de staging de forma idempotente.
- Faz checkout da branch.
- Monta o prompt para o agente, concatenando instruções base com a instrução específica da etapa.
- Executa o agente (Claude Code) via GitHub Action.
- Se houve mudança de arquivos, commita e faz push.
- Se a etapa abre PR, abre como rascunho, por convenção de título.
- Comenta um resumo no card.
- Avança a label automaticamente, disparando a próxima etapa da cascata.
- Em caso de falha, bloqueia o card e comenta o link da execução com erro. E um detalhe fez diferença na prática: algumas etapas declaram que precisam gerar artefatos, arquivos novos ou modificados. Se a etapa roda e nada muda no repositório, o job falha de propósito, porque um agente pode ter um falso positivo sem ter escrito nada - o que entendemos como uma falha silenciosa.
Os erros que travaram o piloto
1. Workflows disparados por eventos de Issue só funcionam a partir do branch default
Se você edita o workflow numa branch de feature, ele não dispara. Todo ajuste nos workflows da esteira precisa chegar na branch principal para valer. Essa foi a causa número um de "por que isso não rodou".
2. Falso-sucesso silencioso
Sem configurar o modo de permissão correto na execução do agente, ele roda com permissões restritas: o comando não escreve nenhum arquivo, mas a execução retorna sucesso mesmo assim. CI verde, board avançando, e nada de fato foi feito. A defesa foi o mecanismo de exigir artefatos: se a etapa deveria gerar arquivos e não gerou, o job falha explicitamente em vez de deixar passar.
3. A autenticação do git quebra depois da execução do agente
A ação que executa o agente reconfigura a autenticação do git no runner, o que faz o git push seguinte falhar com erro de autenticação. Por aqui, a correção consistiu em fazer o push usando uma URL autenticada explicitamente, em vez de depender da configuração padrão do runner.
4. O token padrão do GitHub Actions não dispara outros workflows
Essa é uma proteção anti-loop intencional do GitHub. Se você troca uma label usando o token padrão, isso não dispara os workflows que escutam esse evento. Na prática, o board para de sincronizar e a cascata de etapas é simplesmente interrompida, sem erro nenhum aparente. Nesse caso, a solução foi usar um token de acesso pessoal dedicado para as operações que precisam dessa cascata.
5. O token padrão também não pode abrir Pull Requests
Restrição direta do GitHub Actions em repositórios: por padrão, ele não tem permissão para criar ou aprovar PRs. De novo, resolvido com o token dedicado.
6. Comandos de GraphQL pra listar ou ver PRs exigem escopo de organização que o token não tinha
A solução foi trocar esses comandos específicos por chamadas REST equivalentes, que exigem um escopo mais simples.
Nenhum desses é complexo isoladamente. Juntos, formam o tipo de fricção que trava um piloto promissor na primeira semana se você não souber que eles existem.
Segurança: tratando o conteúdo do Issue como dado não confiável
O corpo e o título do Issue são editáveis por qualquer pessoa com acesso ao repositório, e esse conteúdo entra no prompt do agente. Isso é, por definição, uma superfície de prompt injection.
Assim, as mitigações aplicadas foram:
Repositório privado, com controle de quem pode aplicar labels.
Instrução explícita ao agente para tratar o conteúdo do card como dado, nunca como comando: sem perguntas interativas, sem trocar de branch, sem ações fora do escopo da etapa.
Um delimitador aleatório, gerado a cada execução, para evitar injeção de comandos via variáveis de ambiente do workflow.
Isso não elimina o risco por completo. Qualquer label pode, em tese, disparar um job com permissão de escrita. Para o estágio atual, de uso interno, esse risco residual foi aceito conscientemente. E com isso, chegamos ao próximo passo de hardening, que é uma lista de permissão mais granular de quem pode acionar cada etapa.
Onde estamos hoje
A esteira está rodando há um mês e meio, processando de 4 a 6 specs por semana de forma contínua. E aqui é importante dizer que não é prova de conceito, é uso real e recorrente.
Ao escrever esse artigo, tem um aprendizado que vale a pena compartilhar: no começo, as specs estavam sendo escritas com escopo grande demais. O efeito prático disso é que a implementação gerada pelo agente frequentemente não cobria 100% do que tinha sido especificado - o escopo era grande demais para uma única execução guiada capturar por completo.
Nesse contexto, para corrigir, não mexemos no motor da esteira, mas, sim,no início do processo, com specs menores e mais enxutas, delimitando melhor o escopo. Granularidade da unidade de trabalho importa tanto para um agente quanto importa para um time humano dividir uma sprint - algo que ainda estamos refinando.
Se eu fosse começar de novo
Bom, e aí você me pergunta: e se fosse necessário fazer tudo novamente? Primeiro, eu desenharia a validação de granularidade das specs desde o primeiro dia, em vez de descobrir na prática que specs grandes geram implementações incompletas. E manteria exatamente como está a decisão de intercalar as etapas de validação humana com etapas automáticas: foi essa escolha que tornou possível confiar na esteira sem abrir mão de controle nos pontos que realmente importam.
Se você está pensando em estruturar algo parecido, o conselho mais prático que eu tenho é: comece assumindo que o agente vai retornar sucesso em situações que não são sucesso nenhum, e construa suas próprias redes de segurança para isso. Se há algum segredo, é não confiar só no código de saída da execução.
Top comments (0)