Revisores bot encontram bugs reais — mas as mesmas categorias de bug se repetem entre os PRs. Padrões recorrentes deveriam virar verificações determinísticas, não revisão agêntica.
O Sentry executa três revisores automatizados em seus PRs: sentry[bot] (predição de bugs), sentry-warden[bot] (verificação de correção/segurança) e cursor[bot] (revisão agêntica do Cursor). Combinados, eles postam 23,7% de todos os comentários substantivos de revisão na nossa amostra de alto atrito, e 68,3% dos PRs de alto atrito têm atividade de revisão de bots.
Esses bots não são ruído. Eles encontram bugs reais — erros de tipo do Pydantic, manipuladores de exceção ausentes, divergências de chaves, brechas de segurança. Cada achado é algo que um desenvolvedor precisa ler, avaliar e atender.
Mas aqui está o insight central desta seção:
As mesmas categorias de bug se repetem entre PRs diferentes. Quando o mesmo padrão de revisão agêntica dispara em PR após PR, esse padrão deveria ser promovido de “revisão de LLM cara em cada commit” para “verificação determinística barata em cada commit”.
A revisão agêntica é a ferramenta certa para encontrar um padrão recorrente nas primeiras vezes. É a ferramenta errada para aplicá-lo uma vez que o padrão é conhecido.
O volume
Seção intitulada “O volume”| Métrica | Valor |
|---|---|
| Total de comentários substantivos de revisão de bots | 139 |
| Total de comentários substantivos de revisão humana | 448 |
| Participação dos bots na revisão substantiva | 23,7% |
| PRs com atividade de revisão de bots | 41 / 60 |
| Participação de PRs de alto atrito com bots | 68,3% |
| Comprimento médio dos comentários de bots | 1.565 caracteres |
| Bot | Comentários substantivos | Papel |
|---|---|---|
sentry[bot] | 81 | Revisor interno de predição de bugs |
sentry-warden[bot] | 50 | Verificador interno de correção/segurança |
cursor[bot] | 4 | Revisão agêntica do Cursor (a maioria filtrada como templates) |
github-advanced-security[bot] | 3 | Consultor de segurança do GitHub |
getsentry-bot | 1 | Automação interna |
sentry[bot] e sentry-warden[bot] juntos respondem por 94% das revisões substantivas de bots.
O que os bots estão de fato capturando
Seção intitulada “O que os bots estão de fato capturando”Quando lemos os achados reais dos bots, eles se agrupam em um pequeno número de categorias recorrentes. Os agrupamentos abaixo foram identificados lendo os 139 comentários substantivos de bots da nossa amostra. Cada agrupamento inclui o padrão, exemplos e o tipo de verificação determinística que substituiria a revisão agêntica para aquele padrão.
Padrão 1: tratamento de exceção ausente em fetches de modelo
Seção intitulada “Padrão 1: tratamento de exceção ausente em fetches de modelo”
QuerySubscription.DoesNotExist not handled in get_metric_issue_context—MetricIssueContext.from_group_event()callsQuerySubscription.objects.get(id=...)without handlingDoesNotExist. If the subscription is deleted between alert creation and notification rendering, this will crash. —sentry-warden[bot]em #111674
get_from_cache raises ValueError because DetectorGroup has no cache_fields defined— The baseBaseManagerhas emptycache_fieldsby default, soget_from_cachewill raise. —sentry-warden[bot]em #111714
If
project_idisNoneintrigger_coding_agent_launch,Project.objects.get_from_cachefails silently —sentry[bot]em #111691
Substituir por: uma regra customizada de Ruff/Semgrep que sinalize chamadas Model.objects.get(...) e Model.objects.get_from_cache(...) dentro de funções que não têm um except Model.DoesNotExist: correspondente (ou um try envolvente). Para get_from_cache, verificar também se o modelo define cache_fields. É uma regra de 30 linhas que captura o padrão para sempre.
Padrão 2: .filter().first() com except DoesNotExist inalcançável
Seção intitulada “Padrão 2: .filter().first() com except DoesNotExist inalcançável”
AttributeError on None when Group is not found— The function uses.filter().first()which returnsNonewhen no record exists, but theexcept Group.DoesNotExisthandler is unreachable because.filter().first()never raises exceptions. —sentry-warden[bot]em #111724
Substituir por: um lint de AST trivial: se um bloco try contém .filter(...).first() e a cláusula except captura *.DoesNotExist, sinalizar como inalcançável. Esse padrão é mecânico — nenhum LLM necessário.
Padrão 3: acesso direto a dict em respostas de API externas
Seção intitulada “Padrão 3: acesso direto a dict em respostas de API externas”
KeyError when GitHub API response lacks 'installations' key— Line 908 uses direct dict accessinstalled_orgs["installations"]on the GitHub API response. If the API returns an error or malformed response, this will raiseKeyError. —sentry-warden[bot]em #111728
Substituir por: um ADR que exija que respostas de API externas sejam parseadas por meio de TypedDicts ou modelos Pydantic com validação explícita de campos. Uma regra de lint pode sinalizar acesso direto ["key"] em variáveis que vêm de chamadas requests.get(...).json() ou gh_client.*.
Padrão 4: uso indevido do sistema de tipos em serializers
Seção intitulada “Padrão 4: uso indevido do sistema de tipos em serializers”
TypeError when saving defaultCodingAgent: str | None union type is not callable— ThedefaultCodingAgentoption tuple usesstr | Noneas the type parameter. The code callstype_(data[key])to convert the value. However,str | Noneis atypes.UnionTypewhich cannot be called as a function. —sentry-warden[bot]em #111697
Pydantic validation error when coding_agent is 'seer' with integration_id— The code creates aSeerAutomationHandoffConfigurationwithtarget='seer'. However, the Pydantic model’stargetfield isLiteral['cursor_background_agent', 'claude_code_agent', ...]which excludes'seer'. —sentry-warden[bot]em #111697
Substituir por: configuração mais estrita de mypy/pyright no CI, mais uma verificação customizada de que a anotação de tipo passada para o registro de opções seja um tipo callable concreto, não um UnionType. A divergência de Literal do Pydantic é capturada pelo mypy se o modo strict estiver habilitado no módulo relevante.
Padrão 5: divergência de chave/identificador entre escritor e leitor
Seção intitulada “Padrão 5: divergência de chave/identificador entre escritor e leitor”
Organization option key mismatch causes setting to have no effect— The new option registers with keysentry:default_automated_run_stopping_pointbut the code that consumes this value reads from keysentry:default_stopping_point. Any value set via the API will be stored but never read. —sentry-warden[bot]em #111697
Substituir por: este é um dos achados de bot mais valiosos — e um dos mais fáceis de converter em uma verificação. O Sentry já tem um registro de opções de organização. Um fixture de teste pode afirmar que toda chave escrita via OrganizationOption.objects.set_value(...) corresponde a uma chave registrada, e que todo ponto de leitura (get_value(...)) usa uma chave que existe no registro. Bônus: extrair as chaves de opção para um enum tipado de modo que o sistema de tipos capture o erro de digitação em tempo de edição.
Padrão 6: blocos if/elif não exaustivos causando UnboundLocalError
Seção intitulada “Padrão 6: blocos if/elif não exaustivos causando UnboundLocalError”The
if/elifblock checking thedatatype is not exhaustive. A new subclass ofBaseMetricAlertNotificationDatawould cause anUnboundLocalErrorbecausemetric_issue_contextwould not be assigned. —sentry[bot]em #111674
Substituir por: usar pattern matching estrutural do Python 3.10+ com exaustividade explícita case _:, OU usar o padrão assert_never() do mypy para uniões tagueadas. Ambos são detectáveis por análise estática uma vez que a convenção esteja em vigor.
Padrão 7: atualizações de listas companheiras esquecidas
Seção intitulada “Padrão 7: atualizações de listas companheiras esquecidas”The
python-litestarplatform is missing from several product onboarding arrays inplatformCategories.tsx, which will prevent its onboarding documentation from loading for Logs, Metrics, Profiling, and Replay. —sentry[bot]em #111522
The
python-litestarplatform is missing from the Pythonplatformsset inmonitorQuickStartGuide.tsx, causing it to be miscategorized in the Crons monitor form. —sentry[bot]em #111522
Em um único PR (#111522), o bot encontrou o mesmo tipo de bug quatro vezes separadas — adicionar uma plataforma a uma lista e esquecer as outras listas.
Substituir por: refatorar o registro de plataformas em uma única fonte de verdade que derive todas as listas companheiras de um registro tipado. É uma refatoração única que elimina uma classe inteira de bugs. Como alternativa, uma verificação de consistência no estilo CODEOWNERS: quando um PR modifica platformCategories.tsx, verificar se qualquer novo identificador de plataforma aparece em todos os arrays relevantes.
Padrão 8: tags de métricas de alta cardinalidade
Seção intitulada “Padrão 8: tags de métricas de alta cardinalidade”The circuit breaker for Sentry App webhooks uses a per-app slug for its metrics key, creating high-cardinality data because a
metrics_keyoverride is not provided. —sentry[bot]em #111723
Substituir por: uma regra de lint que sinalize metrics.incr(name, tags={"slug": ...}) (ou padrões similares de alta cardinalidade) sem um override explícito de metrics_key ou um comentário de allowlist. Esse é um antipadrão conhecido com uma correção conhecida.
Padrão 9: estado não reiniciado em blocos catch
Seção intitulada “Padrão 9: estado não reiniciado em blocos catch”The form’s saving state is not reset in the
catchblock on submission failure, leaving the submit button permanently busy and preventing retries. —sentry[bot]em #111490
Substituir por: uma regra de ESLint (ou convenção de custom hook do React) que exija try/finally para qualquer setter de estado dentro de um handler de submissão assíncrono — setSaving(true) no try, setSaving(false) no finally. A convenção previne completamente o bug do caminho de erro.
Padrão 10: verificações de truthiness removidas → None salvo como valor explícito
Seção intitulada “Padrão 10: verificações de truthiness removidas → None salvo como valor explícito”The removal of the truthiness check for
stopping_pointcausesNoneto be explicitly saved as a project option, overriding the intended default value of"code_changes". —sentry[bot]em #111697
Substituir por: um ADR (e uma regra de lint) que diga que setters de opção devem rejeitar None a menos que o schema da opção explicitamente o permita. Combinado com o Padrão 4 (registro tipado de opções), isso se torna uma verificação estática.
Matriz de recorrência de padrões
Seção intitulada “Matriz de recorrência de padrões”| Padrão | PRs na amostra com correspondência | Bot(s) reportando | Custo da verificação determinística |
|---|---|---|---|
Tratamento de DoesNotExist ausente | 3+ | warden, sentry-bot | Baixo — regra de Ruff/Semgrep |
.filter().first() + except inalcançável | 1+ | warden | Trivial — lint de AST |
| Acesso direto a dict em respostas de API | 2+ | warden | Médio — adoção de TypedDict + lint |
UnionType não callable | 1 | warden | Baixo — modo strict do mypy |
Divergência de Literal do Pydantic | 1 | warden | Trivial — modo strict do mypy |
| Divergência de chave de opção (escritor/leitor) | 1 | warden | Baixo — registro tipado de opções |
| if/elif não exaustivo | 1 | sentry-bot | Médio — convenção assert_never |
| Lista companheira esquecida (registro de plataformas) | 1 (4 instâncias) | sentry-bot | Médio — refatorar para fonte única |
| Tag de métrica de alta cardinalidade | 1 | sentry-bot | Baixo — regra de lint |
| Estado de formulário não reiniciado em erro | 1 | sentry-bot | Trivial — regra de ESLint |
None salvo como valor explícito | 1 | sentry-bot | Baixo — validação de schema |
Esses 10 padrões sozinhos respondem pela maioria dos achados substantivos de bots na nossa amostra. Cada um tem um substituto de verificação determinística que custa muito menos do que a revisão agêntica por PR.
O argumento de custo
Seção intitulada “O argumento de custo”A revisão agêntica custa aproximadamente:
- Inferência de LLM: um comentário de revisão de bot de 1.500 caracteres implica centenas de milhares de tokens de entrada (o diff + contexto) e uma saída relevante. Pela precificação atual dos modelos, isso é da ordem de dezenas de centavos por achado.
- Atenção do desenvolvedor: achados de 1.565 caracteres levam de 30 a 60 segundos para ler e avaliar. Multiplicado pelos 139 achados em 60 PRs, isso são cerca de 70-140 minutos de atenção cumulativa do desenvolvedor apenas na nossa amostra. Extrapolado para o volume total de PRs do Sentry, o investimento de tempo é substancial.
- Latência: a revisão agêntica roda depois que o PR é aberto. Um bug encontrado após a abertura é mais caro do que um bug capturado em tempo de edição por um type checker ou regra de lint.
Uma verificação determinística custa:
- Tempo de execução de CI: microssegundos a milissegundos por arquivo.
- Atenção do desenvolvedor em tempo de edição: os erros de lint aparecem na IDE antes mesmo de o PR ser aberto. Capturar o bug em tempo de edição é cerca de 100x mais barato do que capturá-lo na revisão de PR.
- Custo zero de inferência de LLM por PR.
O argumento econômico e ergonômico é claro: todo achado de bot que se repete é candidato à promoção para uma verificação determinística.
O que os bots devem continuar fazendo
Seção intitulada “O que os bots devem continuar fazendo”A revisão agêntica ainda tem um papel legítimo:
- Descoberta de padrões pela primeira vez — encontrar formatos novos de bugs que ainda não correspondem a nenhuma regra conhecida
- Raciocínio entre arquivos — bugs que abrangem múltiplos arquivos de maneiras que regras no nível de AST não conseguem capturar facilmente
- Correção semântica — achados de “este código faz a coisa errada” que exigem entender a intenção
- Revisão de segurança para superfícies de ataque novas — onde o modelo mental de um atacante é necessário
O objetivo não é eliminar a revisão de bots. O objetivo é promover continuamente achados da revisão agêntica para regras determinísticas, de modo que a atenção do bot fique livre para os casos genuinamente novos.
Implicações para as propostas de ADRs
Seção intitulada “Implicações para as propostas de ADRs”Este insight remodela GEN-003 (Converter achados recorrentes de bots em regras determinísticas) — em vez de um “protocolo de triagem” para achados de bots, o ADR certo é um protocolo de promoção: um fluxo de trabalho para converter achados recorrentes de bots em regras de lint, verificações de tipo ou validações de schema.
Ele também fortalece vários outros ADRs:
- BE-001 (Evolução do contrato de API) — muitos achados de bots (divergência de chave de opção, propagação de
None, divergências deLiteraldo Pydantic) são detectáveis se o contrato de API for tipado e orientado por registro - GEN-002 (Matriz de evidência de testes) — os achados de teste dos bots (divergências de asserção, casos extremos ausentes) viram templates de scaffold de teste
- FE-001 (Convenções de frontend) — o achado de bot de estado de formulário não reiniciado vira uma convenção do React, não um comentário de revisão ad hoc
Ressalvas
Seção intitulada “Ressalvas”- Viés de amostra: esta análise é baseada nos 60 PRs de maior atrito, não em todos os 500. A atividade de bots em PRs de baixo atrito pode diferir.
- As contagens de padrões são mínimos: a coluna “PRs na amostra com correspondência” mostra ocorrências na nossa amostra aprofundada de 60 PRs. No volume total de PRs do Sentry, cada padrão provavelmente recorre com muito mais frequência. Uma análise formal de recorrência exigiria taguear os achados de bots sistematicamente por padrão.
- Filtro de substantivo vs. ruído: excluímos comentários de template (resumos BUGBOT_REVIEW, BACKEND_TEST_FAILURES), mas contamos as revisões substantivas de bots no nível da linha.
- Os bots estão evoluindo: os bots internos do Sentry (
sentry[bot],sentry-warden[bot]) são relativamente novos. À medida que mais padrões recorrentes forem promovidos para verificações determinísticas, os achados dos bots devem se deslocar para casos genuinamente novos.