> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/aprender/fundamentos/falhas-retries-e-idempotencia.md).

# Falhas, retries e idempotência

O que acontece quando um command lança exceção, quando uma function falha, quantas vezes o skail tenta de novo, e por que a idempotência do efeito externo continua sendo responsabilidade do seu código.

## Retry sem configurar nada

O mecanismo é a reentrega. Quando um passo falha, o skail entrega a execução de novo à sua aplicação. A function é reexecutada (replay), os commands já concluídos devolvem os resultados gravados, e o passo que falhou roda outra vez. Uma oscilação de rede entre a sua aplicação e o skail não vira falha da execução: o runtime lida com isso antes de contar uma tentativa. O parâmetro `retryCount` dos atributos define quantas reentregas cada passo aceita: 5 para `[SkailCommand]`, 15 para `[SkailFunction]`, por padrão.

## O que acontece quando um command lança exceção

Passo a passo, com um command `CobrarCartao` que chama o gateway de pagamento:

1. O gateway devolve erro e `CobrarCartao` lança `HttpRequestException`.
2. O runtime registra a falha e devolve a execução ao skail. A function não vê nada ainda.
3. O skail reentrega a execução. A function roda de novo desde o início; `ReservarEstoque`, que já tinha concluído, devolve o resultado gravado sem executar. `CobrarCartao` executa de novo.
4. Isso se repete até o command concluir ou até esgotar o `retryCount` (5 tentativas por padrão).
5. Quando esgota, a exceção é lançada dentro da function, no `await CobrarCartao(...)`, como qualquer exceção em `async/await`. É aqui, e só aqui, que o seu `try/catch` entra.

Duas consequências práticas. Primeira: a function não vê as tentativas intermediárias, então um laço de retry manual com `try/catch` dentro dela duplica o que o runtime já fez. Ajuste o `retryCount` do command e trate a exceção final uma vez. Segunda: o runtime não classifica exceções em transitórias ou definitivas. Qualquer exceção gera nova tentativa. Se uma falha é definitiva por natureza (cartão recusado, CPF inválido), não a represente como exceção: devolva um resultado (`ResultadoCobranca.Recusado`) e decida na function.

```csharp
[SkailFunction]
public async SkailTask ProcessarPedido(Guid pedidoId)
{
    await ReservarEstoque(pedidoId);

    ResultadoCobranca resultado;
    try
    {
        resultado = await CobrarCartao(pedidoId);   // exceção só chega aqui depois de 5 tentativas
    }
    catch (HttpRequestException)
    {
        await LiberarEstoque(pedidoId);               // compensação
        await MarcarPedidoComoPendente(pedidoId);
        return;
    }

    if (resultado == ResultadoCobranca.Recusado)      // falha de negócio: não é exceção
    {
        await LiberarEstoque(pedidoId);
        return;
    }

    await ConfirmarPedido(pedidoId);
}

[SkailCommand(retryCount: 8)]
public async SkailTask<ResultadoCobranca> CobrarCartao(Guid pedidoId) { /* gateway */ }
```

Quando o `retryCount` do command deve mudar: para cima em integrações sabidamente instáveis; para zero quando o efeito não pode ser repetido de forma alguma e você prefere tratar a falha na function na primeira ocorrência.

## O que acontece quando a function lança exceção

Se a exceção sai da function (você não capturou, ou relançou), a execução é tratada como falha da function e reentregue até o `retryCount` dela, 15 por padrão. Em cada reentrega os commands concluídos não rodam de novo; só o trecho que ainda não tinha passo executa. Quando as 15 se esgotam, a execução é encerrada como falha e aparece assim no Monitor, com a exceção e a linha do tempo. Depois de corrigir a causa (um dado no banco, um serviço fora), ela pode ser retomada manualmente: [Retomada manual de execuções falhas](/operar/retomada-manual-de-execucoes-falhas.md).

Um detalhe que costuma surpreender: uma exceção de command que esgotou as tentativas e não foi capturada faz a function ser reentregue 15 vezes, e a cada uma o command roda de novo suas 5 tentativas. Se você não quer isso, capture na function e decida.

## O efeito é "no mínimo uma vez"

O que o skail garante: depois que um command grava o resultado no histórico, ele nunca mais executa para aquela execução. O que o skail não pode garantir: que o efeito externo aconteceu exatamente uma vez. Se o command chamou o gateway, o gateway cobrou, e o processo caiu antes de o runtime gravar o resultado, a próxima tentativa chama o gateway de novo.

A janela é pequena, mas existe em qualquer sistema distribuído, e o jeito de fechá-la é o mesmo em todos: idempotência no lado de fora, com uma chave de negócio. Passe o id do pedido, da fatura, da tentativa como chave de idempotência na chamada externa; o gateway, o ERP ou o seu próprio banco reconhecem a repetição e não duplicam o efeito. [Como escrever commands idempotentes](/construir/escrever-fluxos/como-escrever-commands-idempotentes.md) mostra o padrão em código.

Dizer isso ao seu time é ganho, não perda: quem já operou filas sabe que "exatamente uma vez" no efeito não existe, e desconfia de quem promete.

## Resumo das regras

Command: qualquer exceção gera nova tentativa até `retryCount`; a function só vê a exceção depois disso. Falha de negócio é resultado, não exceção. Function: exceção não capturada gera reentrega até o seu `retryCount`; commands concluídos não repetem; ao esgotar, a execução fica como falha no Monitor e pode ser retomada. Efeito externo: no mínimo uma vez; idempotência por chave de negócio é sua.

## Próximos passos

[Como tratar erros e compensar](/construir/escrever-fluxos/como-tratar-erros-e-compensar.md) tem o padrão completo com Saga. [Como escrever commands idempotentes](/construir/escrever-fluxos/como-escrever-commands-idempotentes.md) fecha a janela do "no mínimo uma vez". A referência de [SkailCommand](/construir/sdk-.net/skailcommand.md) descreve o parâmetro `retryCount`.
