Uma execução de cron perdida deve ser recuperada? grace_minutes responde job por job


Você fecha o notebook para uma reunião de duas horas. Ao abrir de novo, há dois relatórios diários idênticos esperando — o job das 15:00 perdeu o horário enquanto a máquina dormia, foi recuperado ao acordar e então disparou outra vez às 16:00. Você apaga a duplicata e pensa “da próxima vez, é só pular”.

Só que a recuperação existe por um motivo. Se a sincronização de dados das 03:00 foi perdida porque a máquina reiniciou, você quer que ela rode logo depois do boot, não amanhã. A pergunta nunca foi se recuperar é bom; é por quanto tempo um job específico deve estar disposto a esperar. O Hermes aplicava uma regra única a todos os jobs. Agora cada job pode definir a sua.

O que o padrão realmente faz

O agendador encontra um horário devido no passado e tem duas opções: executar agora, ou pular e reancorar na próxima ocorrência. A decisão usa uma janela de recuperação:

  • metade do período do job,
  • limitada entre 2 minutos e 2 horas.

Assim, um job que roda a cada minuto tem janela de 2 minutos: qualquer coisa mais atrasada é considerada velha e ele salta para o próximo minuto em vez de disparar uma rajada de execuções de recuperação. Um job diário ganha 12 horas, então reabrir o notebook no meio da manhã ainda conta como “atrasado, mas válido”. Um job horário ganha 30 minutos.

Período Janela de recuperação padrão
A cada minuto 2 minutos (piso)
A cada 15 minutos 7,5 minutos
A cada hora 30 minutos
Diário 12 horas
Semanal 2 horas (teto)

Jobs de execução única são um caso à parte: recebem 120 segundos de tolerância e depois expiram.

A regra é sensata — períodos longos toleram mais atraso, períodos curtos avançam rápido — mas encaixa mal em dois cenários comuns:

  • Relatórios diários ou semanais que devem rodar no máximo uma vez por dia. Uma janela de 12 horas significa que o job dispara ao acordar, mesmo que você já tenha conferido os números no celular, gerando notificações duplicadas.
  • Jobs sensíveis a horário. Puxar dados de mercado 30 minutos antes da abertura não serve para nada às 11:00. Melhor pular.

Sobrescrevendo a janela por job

A sobrescrita é em minutos:

# nunca recuperar: uma ocorrência perdida é descartada
hermes cron edit market-open --grace-minutes 0

# aceita rodar com até três horas de atraso
hermes cron edit nightly-sync --grace-minutes 180

Você também pode definir na criação:

hermes cron create "30 9 * * 1-5" "Busque as cotações antes da abertura e escreva o briefing" \
  --name market-open \
  --grace-minutes 5

O intervalo vai de 0 a 1440 minutos (um dia inteiro). 0 é um valor de verdade, que significa “nunca recuperar”. Para voltar à janela calculada, passe uma string vazia:

hermes cron edit nightly-sync --grace-minutes ""

Isso vale apenas para expressões cron. Agendamentos once e interval não têm horário de relógio perdido — interval conta a partir da última execução e once é um horário absoluto — então definir o valor neles não faz nada, e o CLI avisa em vez de aceitar em silêncio.

Três configurações típicas

Relatórios diários e semanais: 0. O que importa é se rodou hoje, não se a cópia de ontem acaba aparecendo. Com 0 a ocorrência perdida é descartada, o descarte fica registrado no log e o próximo horário roda normalmente. As notificações duplicadas acabam.

Jobs noturnos em uma máquina que dorme: use um valor alto. Quem usa notebook é o caso clássico: sincronização às 23:00, máquina que liga às 08:00. A janela padrão cobre um job diário, mas o semanal fica limitado a 2 horas, então use --grace-minutes 600 e ele ainda recupera pela manhã.

Jobs sensíveis a horário: mantenha pequena. Jobs de pré-abertura, pré-lançamento ou pré-conciliação não servem para nada atrasados. Dê 5 ou 10 minutos e deixe que sejam pulados. Um relatório desatualizado que roda com sucesso é mais perigoso do que um job que não rodou, porque ninguém percebe.

Desligando a recuperação globalmente

Se você não quer recuperação em lugar nenhum:

hermes config set cron.catch_up_missed false

Um job recorrente que passou da janela passa a ser reancorado na próxima ocorrência futura em vez de disparar, e o descarte é registrado no log. Jobs ainda dentro da janela, além de disparos manuais, seguem o comportamento normal. Em relação ao ajuste por job: o switch global é um desligamento, o valor por job afrouxa ou aperta a janela de um job — uma vez que a recuperação esteja desligada globalmente, uma janela generosa por job não tem sobre o que agir.

Em deployments hospedados com um provider de cron gerenciado existe mais uma camada: cron.misfire_grace_minutes (padrão 10) é por quanto tempo o gateway espera a nova tentativa do próprio provider antes de recuperar localmente, de modo que uma entrega falha custa minutos em vez do dia inteiro.

Quatro armadilhas

  • 0 é um valor, não “não definido”. Os comentários do código deixam isso explícito: 0 == False, então qualquer teste do tipo “vazio significa limpar” descarta 0 em silêncio e restaura a janela padrão. O CLI e o parsing de configuração tratam um 0 explícito como definido — se você editar o jobs.json por script, tenha o mesmo cuidado.
  • Só expressões cron. Agendamentos interval e once ignoram o campo; ele não vai controlar como um job “a cada 5 minutos” recupera execuções perdidas.
  • A distribuição de profiles descarta campos desconhecidos. Se você exporta e distribui definições de job entre profiles, grace_minutes precisa estar na lista de campos permitidos, senão o lado que recebe volta para a janela padrão.
  • Os health checks mudam junto. A linha “este job está atrasado” do dashboard usa a mesma janela do agendador. Mudar a tolerância muda também o limite do alarme — o que está correto (senão o dashboard sinalizaria jobs que o agendador considera em dia), mas o momento dos seus alertas vai mudar.

Confirmando que surtiu efeito

hermes cron list                 # a linha Grace aparece apenas quando há uma sobrescrita
hermes cron doctor               # aponta um valor inutilizável, ou definido em um agendamento não-cron

Em hermes cron list, o bloco de detalhes de cada job ganha uma linha Grace mostrando algo como 180 min. Jobs sem sobrescrita não têm essa linha. O hermes cron doctor verifica dois modos de falha: um valor inutilizável e um valor definido em um agendamento que não consegue usá-lo. Para confirmar que um descarte realmente aconteceu, procure a linha de log Skipping missed occurrence — ela informa a causa (grace_minutes ou o global cron.catch_up_missed=false) e o horário da próxima execução.

Para um passo a passo completo de health check, veja o guia do cron doctor; para disparos errados causados por fuso horário, o artigo sobre fuso horário e horário de verão; para montar jobs do zero, o guia de automação com cron.

Status

O grace_minutes chegou ao main em 2026-10-28 e já vem na v0.22.6 (2026-11-02): basta atualizar para definir a janela de recuperação por tarefa. Em builds anteriores, a alternativa é o switch global cron.catch_up_missed: tamanho único, mas suficiente para parar com as recuperações duplicadas.