← Tutoriais
BUILD · AGENTES

Meus agentes agendados rodaram código velho por dias e nunca me avisaram

Uma quebra você percebe. Um agente que roda código velho em silêncio e reporta OK é o que te custa quatro dias. O conserto é um passo de sincronização que é barulhento quando falha.

Para
Qualquer um que rode agentes em um agendamento a partir de um checkout na própria máquina
Precisa
Um agente agendado (Cowork, cron, CI) e um repositório git de onde ele roda
Tempo
20 minutos

Um agente de revisão de código que rodo toda manhã relatou “nenhum commit novo nas últimas 36 horas.” Eram dez. O agente estava lendo uma cópia do meu repositório que estava congelada havia quatro dias, e nada no sistema inteiro dizia isso. Ele não quebrou. Não me avisou. Apenas fez seu trabalho em silêncio sobre código velho e relatou que deu tudo certo.

Esse é o modo de falha sobre o qual ninguém te avisa quando você começa a rodar agentes em um agendamento. Uma quebra você percebe. Trabalho desatualizado que reporta OK, não.

O que de fato quebrou

Rodo várias tarefas do Aldeia, um dos meus projetos, em um agendamento via Cowork: um autopublicador de avisos, uma revisão de código diária, uma passada de segurança. Todas executam a partir de um único checkout na minha máquina, em /Users/edra/Documents/Claude/aldeia.

Dois dias antes eu tinha implantado uma reescrita da autopublicação de avisos. Ela “nunca havia disparado.” Quando rodei a publicação na mão para depurar, a execução bateu no script velho e escreveu uma linha com status pending em avisos_submissions, em vez de colocar o aviso no ar em avisos como o código novo faz. O código novo estava em origin/main. O checkout não estava rodando ele.

A pista veio de outro agente. O artefato de revisão de código daquela manhã citava um hash de commit de quatro dias atrás e concluía que não havia tido “nenhum commit em 36 horas.” Um agente agendado que descreve com confiança um commit velho como o topo está te dizendo que o runner dele está desatualizado, se você estiver prestando atenção. Eu não estava, por mais ou menos uma hora.

A causa raiz

O checkout estava preso em f4adaf8, dez commits atrás de origin/main. Todo git pull e git commit ali vinham falhando, em silêncio, desde que um arquivo de lock obsoleto apareceu:

$ git -C /Users/edra/Documents/Claude/aldeia rev-parse --short HEAD
f4adaf8
$ git -C /Users/edra/Documents/Claude/aldeia log --oneline HEAD..origin/main | wc -l
10
$ ls -la /Users/edra/Documents/Claude/aldeia/.git/index.lock
-rw-r--r--  1 edra  staff  0 Jun 19 05:34 .git/index.lock

Um .git/index.lock de zero bytes, datado de 19 de junho às 05:34, bem no meio da janela matinal do Cowork. Uma operação de git morreu no meio da escrita (o Mac dormiu durante uma tarefa) e deixou o lock para trás. O git trata esse lock como “outro processo de git está trabalhando aqui, se afaste,” então toda escrita posterior é abortada. Como o lock nunca foi o lock ativo de um processo vivo, nada o limpou. O checkout ficou congelado por quatro dias enquanto os agentes agendados seguiam rodando contra ele.

O conserto que não basta

O conserto óbvio é uma linha:

rm -f .git/index.lock && git pull --ff-only

Isso resolve este incidente. Não faz nada para evitar o próximo, e é perigoso como reflexo. Apagar index.lock enquanto um processo de git está legitimamente segurando ele corrompe seu índice. Então a regra real é: só limpe o lock quando não houver nenhum processo de git vivo.

# obsoleto SÓ se o arquivo existir E não houver processo de git rodando
[ -f .git/index.lock ] && ! pgrep -f '[g]it ' && rm -f .git/index.lock

Mas o problema de fundo nunca foi o lock. Foi que o congelamento era silencioso. O sistema precisa se sincronizar antes de cada execução, e precisa ser barulhento quando não consegue.

O conserto que basta

Coloquei um cowork-sync.sh no repositório e fiz dele o primeiro passo de toda tarefa agendada. Ele se auto-cura levando o checkout para origin/main, e sai com código diferente de zero em qualquer falha, para que uma sincronização quebrada alerte em vez de enviar código velho.

A parte que quero que você copie é o desenho de segurança. Existem dois tipos de checkout, e eles precisam de tratamento oposto:

Separo os dois modos com um arquivo marcador ignorado pelo git, .cowork-checkout. Marcador presente significa descartável, force. Marcador ausente significa compartilhado, sincronize com cuidado. Um reset destrutivo, portanto, só pode tocar um checkout que eu marquei explicitamente como descartável.

#!/usr/bin/env bash
set -euo pipefail
REPO="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
cd "$REPO"

# Limpa um lock OBSOLETO só quando não há processo de git vivo.
if [ -f .git/index.lock ] && ! pgrep -f '[g]it ' >/dev/null 2>&1; then
  rm -f .git/index.lock
fi

PREV="$(git rev-parse --short HEAD)"
git fetch origin --quiet

if [ -f "$REPO/.cowork-checkout" ]; then
  # Checkout descartável do agente — force ao remoto.
  git reset --hard origin/main --quiet
  git clean -fd --quiet
else
  # Checkout compartilhado — só sincronização segura, parar BARULHENTO se bloquear.
  if ! git merge --ff-only origin/main --quiet; then
    echo "cowork-sync: ff-only blocked on $REPO — NOT forcing." >&2
    exit 4
  fi
fi

echo "cowork-sync: OK ${PREV}→$(git rev-parse --short HEAD) (${REPO})"

Dois detalhes que vale a pena manter. clean -fd apaga arquivos não rastreados mas mantém os ignorados pelo git, então nunca apaga seu .env.local nem node_modules. Nunca adicione -x. E se um commit sincronizado mudou o lockfile, avise mas não instale automaticamente. npm ci apaga node_modules, o que é destrutivo se o seu for um symlink.

A regra

Um agente que quebra é um bom agente. Ele te avisa que algo está errado. Um agente que roda código velho e reporta sucesso é o que te custa quatro dias, porque parece exatamente igual a estar tudo bem.

Se você roda qualquer coisa em um agendamento a partir de um checkout que também mexe na mão, faça da sincronização o primeiro passo, faça a falha ser barulhenta, e nunca aponte um reset --hard para uma árvore que guarde trabalho que você sentiria falta. O congelamento vai acontecer. A única coisa que você controla é se ele fica em silêncio.

ACHOU ÚTIL? RECEBA O PRÓXIMO

Deixe seu e-mail e eu te aviso quando o próximo sair, com as ferramentas e fluxos que eu de fato uso. Sem spam.

Mais tutoriais ↗