Reagenda entrega de webhook quando a falha é temporária - #86
Merged
Merged
Conversation
Fecha #76. WebhookDeliveryJob chamava WebhookDelivery.entregar e descartava o retorno. Como o serviço capturava tudo, logava e devolvia false, o ActiveJob dava a entrega por concluída em qualquer cenário: endpoint fora do ar, timeout, 5xx — tudo virava um aviso no log que ninguém reprocessava. O retorno de #entregar passa a ser a decisão: * true — entregue (2xx); * false — recusa definitiva (assinatura pausada ou excluída, URL reprovada na checagem de SSRF, 4xx, erro de TLS): o job encerra sem gastar tentativa, porque repetir não mudaria a resposta; * FalhaTemporaria — erro de rede, 429 ou 5xx: única exceção que escapa de propósito, e é o que o retry_on do job observa. Cinco tentativas com backoff polinomial. O bloco do retry_on registra a desistência em nível error — sem ele, a entrega perdida voltaria a sumir em silêncio, só que na quinta falha em vez da primeira. Duas classificações que não são óbvias, e por isso estão comentadas no código: erro de TLS não repete (certificado errado é configuração do outro lado, e repetir mantém uma assinatura quebrada parecendo viva), e erro inesperado também não (é bug nosso; repetir não conserta e atrasa o diagnóstico). O rescue de FalhaTemporaria precisa vir antes do rescue de StandardError: como ela herda dele, sem essa cláusula a exceção seria engolida e viraria de novo o false silencioso que esta mudança elimina. Junto vem config.active_job.queue_adapter = :test no ambiente de teste. Sem isso valia o :async, que roda o job de verdade numa thread de fundo durante o teste — contra o mesmo banco, dentro de uma transação que o RSpec desfaz, e com chance de tentar rede. Hoje quase nunca disparava porque os specs dublam perform_later, mas isso era acidente, não desenho; e é o adapter :test que permite verificar o retry sem esperar backoff real. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fecha #76.
WebhookDeliveryJobchamavaWebhookDelivery.entregare descartava o retorno. Como o serviço capturava tudo, logava e devolviafalse, o Active Job dava a entrega por concluída em qualquer cenário — endpoint fora do ar, timeout, 5xx. Tudo virava um aviso no log que ninguém reprocessava.O retorno passa a ser a decisão
2xxtrue4xx, erro de TLSfalse429,5xxFalhaTemporariaFalhaTemporariaé a única exceção que escapa do serviço, de propósito — é exatamente o sinal que oretry_onobserva.Três decisões que valem explicar
Erro de TLS não repete. Certificado expirado, inválido ou com nome errado é configuração do outro lado. Repetir cinco vezes não conserta e mantém uma assinatura quebrada parecendo viva. Fica de fora de
ERROS_TEMPORARIOSdeliberadamente, com o motivo no código.Erro inesperado também não repete. Se algo estourar que não seja instabilidade do outro lado, é bug nosso — repetir não conserta e atrasa o diagnóstico. Vai para o log em nível
error(antes erawarn) e encerra.O bloco do
retry_onexiste porque a alternativa é silêncio. Sem ele, esgotadas as tentativas a entrega sumiria sem rastro — o mesmo problema desta issue, só que adiado da primeira falha para a quinta.Uma sutileza que quase reintroduziu o bug
O
rescue FalhaTemporariaprecisa vir antes dorescue StandardError. Como ela herda dele, sem essa cláusula a exceção levantada na avaliação da resposta seria engolida pelo rescue genérico e viraria de novo ofalsesilencioso que este PR elimina. Está comentado no código, porque é o tipo de linha que alguém remove achando redundante.Mudança de ambiente que veio junto
config.active_job.queue_adapter = :testno ambiente de teste. Sem isso valia o:async, que roda o job de verdade numa thread de fundo durante o teste — contra o mesmo banco, dentro de uma transação que o RSpec vai desfazer, e com chance de tentar rede real viaWebhookDelivery.Hoje isso quase nunca disparava, porque os specs de dispatcher dublam
perform_later. Mas era acidente, não desenho. E é o adapter:testque permite verificar o reagendamento sem esperar backoff real.O que este PR não resolve
O adapter em produção continua sendo o
:asyncem memória, então as tentativas reagendadas também se perdem num restart — é a #77. O retry aqui cobre a instabilidade curta, que é a maioria dos casos, e vale independentemente do backend: mesmo com Solid Queue, um job que nunca levanta exceção nunca seria repetido. Registrado no README.Verificação
All is good!Cobertura nova: cada classificação de falha verificada isoladamente (rede, timeout, 5xx, 429, TLS, erro inesperado) e as três decisões do job (reagenda no temporário, não reagenda no definitivo nem no sucesso).
Resolvi as duas ofensas de RuboCop que a mudança gerou sem abrir exceção no
.rubocop.yml:Class.new(StandardError)virou declaração de classe, e o método que avalia a resposta passou a se chamarentregue?, que é o que ele de fato responde.🤖 Generated with Claude Code