Skip to main content

Command Palette

Search for a command to run...

Como editar o título de uma PR derrubou todos os builds do dia

Updated
13 min read
Como editar o título de uma PR derrubou todos os builds do dia
V
Documentando o que faço, resolvo e quebro. Foco total em problemas reais e no passo a passo da investigação. Escrevo para organizar como minha mente pensa diante dos problemas e deixo aberto para quem quiser acompanhar.

A pipeline de build de uma aplicação Angular parou de funcionar. O famoso "DO NADA".

Um dev me pingou: "tá dando um erro estranho na pipe, consegue dar uma olhada?" Pensei: não teve mudança recente na pipeline, nem na VM. Estranho. Mas como o assunto é fluxo do time, então é prioridade. Parei o que estava fazendo e fui verificar.


O problema

Acessei o build e de primeira já verifiquei que todos falhavam no mesmo ponto: a etapa de instalação do Angular CLI. Isso já me deu o palpite de que podia ser um problema na VM, pois essa pipeline usa um agente self-hosted.


A investigação

Abri a task de instalação do Angular CLI no Azure DevOps. A primeira coisa que saltou foi ##[error]Error: Npm failed with return code: 217:

Uma pesquisa rápida e descubro que é um erro genérico, ou seja, pode ser qualquer coisa. Tudo bem, mas isso significa que a resposta não estava ali. Voltei para o log, continuei lendo o que estava antes do 217 e encontrei a mensagem: directory not empty, rename '.../cli' -> '.../cli-Xuqet0PR'

Perfeito. Agora eu tinha uma pista. A própria mensagem de erro é intuitiva, o npm tentou instalar o Angular CLI e, para fazer isso, primeiro tenta renomear o diretório atual (cli) para um temporário (.cli-Xuqet0PR). Só que esse temporário já existia com conteúdo. O rename falhou, o processo morreu, o build foi junto.

A grande pergunta não era o que quebrou. Era por que isso estava ali.

Me fazer essa pergunta me fez enxergar a origem do problema em vez de sair procurando solução cegamente. Fui na lista de builds no Azure DevOps, procurei o último lote de builds com sucesso e identifiquei o momento exato em que as falhas começaram. Abri o build correspondente, acessei a PR em andamento e encontrei uma alteração no YAML da pipeline: o dev tinha removido a etapa de instalação do Angular.

Por que esse dev precisou retirar isso? Esse tipo de mudança na pipeline normalmente é um sinal de que o problema foi interpretado sem contexto suficiente.

Antes de investigar mais a fundo, preciso liberar o fluxo

Pipeline parada é prioridade máxima. Como DevOps, minha função é acelerar o ciclo de entrega do time, e build travado é o oposto disso.

Dei um change request na PR, chamei o dev no privado para avisar sobre o erro e comuniquei ao time no grupo que havia um problema na pipeline e que eu já estava investigando. Com isso feito, ativei a VPN e acessei a VM via SSH.

Tava lá. O diretório temporário abandonado, exatamente onde o erro dizia.

Antes de rodar qualquer comando, fui pesquisar o que era esse .cli-Xuqet0PR. Uma regra de ouro aqui é sempre questionar as próprias decisões. Sair apagando arquivos sem entender o impacto é pedir para ter problema. Fiz a pesquisa e retornou essa resposta:

O diretório temporário que o npm utiliza durante uma instalação global serve como um espaço de trabalho intermediário para processar o pacote antes de movê-lo para o local definitivo no sistema.

Ótimo. O fluxo normal do npm é criar o temporário (.cli-xxxxx), processar o pacote nele e mover tudo para o diretório definitivo (cli). Se o processo é interrompido no meio, o temporário fica lá. Na próxima execução, quando o npm tenta criar de novo, encontra um diretório não vazio e explode com ENOTEMPTY.

Agora que entendi o processo e sei qual é o problema, a solução é deletar as duas pastas, a cli e a temporária.

Rodei a pipeline de novo e passou. Fui conferir a pasta na VM logo após o step de instalação do Angular CLI concluir e apenas o diretório cli estava lá, sozinho. Ou seja, o temporário foi criado, extraiu o pacote e depois foi renomeado para cli. Problema resolvido, time avisado no grupo. Agora que o fluxo do time voltou, podemos entender o que causou isso.

Quem interrompeu o processo?

Um princípio que carrego pra sempre na minha carreira é que nada acontece do nada.

De cara, minha dedução foi que o problema estava na alteração do YAML feita pelo dev. Mas fui checar o histórico da PR e notei que ele só removeu a instalação do Angular no terceiro commit. O erro 217 já estava quebrando a pipeline antes disso. O que provavelmente aconteceu? Sem saber como resolver o problema original, ele colou o log na IA. Ela, completamente sem contexto da nossa aplicação, deu a solução mais genérica possível: mandar apagar a instalação do Angular CLI.

Descartada a primeira ideia, pensei que uma possível causa fosse o processo de instalação ter sido abortado no meio. O diretório temporário ficou órfão lá no agente de build. Todos os pipelines seguintes tentavam fazer o rename e quebravam justamente porque esbarravam num destino que já não estava vazio. Mas quem pode ter interrompido a execução, se não tem build anterior ao que falhou?

Fui ver nos logs do agente na VM. Aqui aprendi algo que eu não sabia na prática: a VM está em UTC e eu estou em UTC-3. O build que eu via no Azure DevOps como falho às 08:53 correspondia a 11:53 na VM. Isso importa na hora de correlacionar logs.

Dei um cat nos arquivos de logs do agente e o arquivo era enorme, sem chance de ficar procurando linha por linha. Pedi ajuda a uma IA para refinar o comando, e ela sugeriu filtrar pelo Running job:

Esse comando retornava os logs de todas as vezes que um build foi iniciado usando o agente da VM. Aqui foi fundamental entender a diferença de fuso horário, porque a máquina roda em UTC. Eu precisava isolar os logs e encontrar o primeiro build do dia.

Comecei a caçar pela data e esbarrei em vários logs antes das 11:53, que era o horário da nossa falha. Precisei analisar um por um por processo de eliminação. Primeiro, vi três execuções às 01:00 UTC. Por que eles rodaram antes da primeira pipeline do dia? Simples, foi por conta do fuso (UTC-3), esses builds na verdade rodaram às 22:00 do dia anterior no meu horário local. Eram jobs de outra pipeline que utiliza também esse agente.

Depois, achei mais três jobs às 02:00 UTC. Mesma lógica, eram das 23:00 da noite anterior, referentes aos agendamentos que configurei para rodar em três branches diferentes. Tudo certo até aqui.

Eliminando esse histórico noturno e o build das 11:53 que falhou, sobraram exatamente dois jobs nos logs (às 11:49 e 11:50) que simplesmente não apareciam na interface do Azure DevOps.

Foi só nesse momento que a minha ficha caiu e eu olhei para o número do build que havia falhado: #20000000.3. Estava na minha cara o tempo todo. O sufixo já indicava que era a terceira execução do dia. Eu não tinha prestado atenção nisso antes... mas acontece xD. Isso confirmava tudo, o log me levou na direção certa e realmente tínhamos dois builds perdidos.

Lendo os logs dos Workers

Na pasta _diag/ existem logs de Workers além dos logs do agente. Basicamente, um arquivo de log por build, mas com um detalhe muito útil: o nome do arquivo já contém o horário de início do build, em UTC.

Listei os arquivos e identifiquei esses dois na hora. Os segundos não batem exatamente, mas os minutos batem com 11:49 e 11:50 dos logs do agente, só podiam ser esses.

Rodei o comando para ver o conteúdo e percebi que era denso demais para ler direto. Copiei o log e perguntei para a IA: "o que aconteceu com esse build? Foi deletado, cancelado, crashou? E quem fez isso?" A IA retornou:

Agora eu sabia que os dois builds foram cancelados. Não foi crash, não foi a VM. Receberam um sinal externo limpo. Mas de quem?

O culpado

Ou seja, tínhamos builds cancelados e que sumiram da interface do Azure DevOps. Mas o rastro sempre fica na infraestrutura. Fui atrás e descobri que o Azure DevOps tem uma API REST que expõe justamente esses dados fantasmas. Abri a documentação, levantei os parâmetros necessários e usei a IA para me ajudar a montar a URL rapidão. Como eu já estava logado no portal, a chamada retornou um JSON gigantesco com os detalhes da execução. Joguei tudo num formatador online, dei um Ctrl+F pelo ID do build e a resposta estava lá:

Pois é. O culpado não era o dev novato. Não era a máquina. Não era ninguém do time. Foi o pr.autoCancel em ação... fazendo exatamente o que foi configurado pra fazer. O Azure cumpriu o contrato. O problema real é que a pipeline não estava preparada pra lidar com essa interrupção, e deixou rastro em disco.

Esse recurso cancela automaticamente qualquer build em andamento toda vez que a PR é atualizada. Então fui até a PR no Bitbucket para ver se a linha de pensamento estava certa, e quando cheguei lá vi o seguinte processo:

Feito, problema 100% rastreado.
O que aprendemos com tudo isso? O que aprendemos com tudo isso? A pipeline precisa ser resiliente o suficiente pra sobreviver a cancelamentos. xD

O que aconteceu aqui foi que a PR passou por varias etapas uma em seguida da outra. Ela foi aberta, passou para draft, mudou o título. O dev não percebeu que cada clique inofensivo dele na tela disparava um webhook que mandava o Azure DevOps executar o pr.autoCancel lá no servidor. É claro que a pipeline tem uma fragilidade por não saber limpar a sujeira que o npm deixou para trás. ..

PR é lugar de iterar. Abrir, ajustar o título, passar pra draft, receber review, commitar de novo... isso é o fluxo normal e esperado. O dev não fez nada errado ao atualizar a PR. O que falhou foi a pipeline assumir que o ambiente sempre estaria limpo, sem verificar o estado antes de instalar. É claro que entender que cada clique na interface pode disparar um webhook que impacta um processo rodando em disco lá na VM é um aprendizado valioso. Mas a lição principal aqui é sistêmica: job que não consegue sobreviver a uma interrupção vai quebrar cedo ou tarde.


Ficou em aberto

Por que o npm não faz cleanup ao receber o sinal de cancelamento? Dependendo de como o sinal é entregue e em que ponto exato do rename o processo estava, o cleanup pode não acontecer. Não investiguei isso a fundo. Mas é interessante...

O pr.autoCancel tem configuração granular? Não verifiquei se é possível desativar por pipeline específica ou se é uma configuração global.

Dá para tornar o step idempotente? Adicionar uma limpeza antes do npm install -g eliminaria o problema independentemente de qualquer cancelamento futuro:

Não implementei ainda. Não sei se gera efeito colateral em builds paralelos no mesmo agente.


Referências


Vinicius Aguilar — DevOps Engineer

War Room

Part 3 of 3

Incidentes reais, investigações reais. Cada post aqui é um problema que apareceu em produção com o erro, o raciocínio da investigação e a causa raiz. Sem filtro, sem romantizar. É assim que as coisas funcionam na prática.

Start from the beginning

Por que o Slot de QA não é mais o lugar certo para o Rollback

Chegou mais uma segunda-feira, dia de entrega, e com ela, a rotina de deploy. Nosso processo padrão envolve subir a nova versão em QA e, em seguida, fazer o swap para produção. Simples, certo? Não exa