O Hermes acabou de te pedir para passar por uma verificação de browser? Entenda o que é o challenge 428 no free tier

São duas da manhã. O seu cron job está rodando num servidor, usando o free tier para uma rodada de pesquisa. Não há erro nos logs — só um link que alguém imprimiu, e então o job falha em silêncio. De manhã você lê aquilo e não faz ideia do que era aquele link, por que ele apareceu, ou se você deveria ter clicado nele.
Nada no seu setup está quebrado. O free tier simplesmente ganhou um portão na frente agora. O PR #133824, mesclado em 6 de outubro, ensina o Hermes a lidar com esse caminho direito — e, mais importante, garante que ele nunca fique ali esperando quando não há ninguém para esperar.
Quando o portão aparece
A inferência anônima gratuita passa por POST /api/anonymous/token para trocar por um token. O portal pode ser configurado para exigir uma verificação de browser de faixas de IP ou países escolhidos antes de conceder capacidade anônima. Quando essa regra dispara, aquele endpoint responde HTTP 428 com o código challenge_required e uma URL do portal.
A parte importante: isso é política do lado do portal, ajustável a qualquer momento sem uma release do Hermes. Então “funcionou bem semana passada” geralmente não tem nada a ver com a sua máquina.
Uma primitiva, três passos
O Hermes implementa exatamente uma primitiva para isso, chamada browser-v1:
- abrir a URL que o portal entregou;
- fazer polling de
/api/anonymous/challenge/statusaté que ele libere; - trocar por um token de novo.
Tudo o que a própria página faz — detecção de bot, proof of work no browser, um fallback interativo, todo o seu texto — pertence ao portal e pode mudar sem você atualizar nada. E a mint continua sendo a autoridade: o Hermes nunca decide localmente que uma verificação passou.
A troca envia um bloco client autodeclarado (surface, versão, plataforma, mais capabilities: ["browser-v1"]) e um user agent hermes-agent/<version>.
Quem abre a janela
O comportamento difere por surface, e é isso que responde ao “por que eu não vi nada”:
- Desktop: esse processo não abre nada por conta própria. Ele publica o challenge (um evento global
free_tier.challenge, mais o campochallengeemfree_tier.statuspara um cliente que se conecta depois); o renderer o entrega ao processo principal do Electron, que o carrega numa janela oculta, em sandbox e com origin fixa. Essa janela só é revelada se a página pedir explicitamente por um humano. - Terminal: imprime o link e o abre no browser do sistema quando existe um gráfico.
- A espera roda fora dos locks: a troca que bateu no 428 já desenrolou a sua exceção para fora dos blocos
with, então uma página lenta nunca trava um perfil ou processo irmão. Um challenge é trabalhado por vez por processo; uma segunda thread que bate no mesmo 428 espera pela primeira e depois simplesmente troca de novo.
Quando é que ele não vai te pendurar de jeito nenhum
Os pontos mais arriscados estavam na primeira versão deste trabalho, e corrigi-los é a maior parte do que este PR faz. São todos formatos conhecidos:
- Um gateway nunca espera. Gateways resolvem credenciais da Nous de forma síncrona a partir de código assíncrono, então um 428 parava o event loop por até 120 segundos — e ninguém fica no console de um bot para passar numa verificação de browser. A surface de gateway agora registra, anuncia e levanta um erro retentável imediatamente.
- O adapter de proxy não trava o lock. Ele resolve credenciais enquanto segura o lock no qual toda requisição via proxy faz fila, então uma pausa de 120 segundos bloqueava tudo. Agora ele resolve como um chamador em segundo plano.
- Leitores em segundo plano nunca abrem nada. Ticks de keepalive e pinturas de status recebem o challenge mas nunca bloqueiam, imprimem ou abrem um browser.
- A TUI stdio finalmente te avisa. Ela costumava guardar o stderr do processo filho do gateway apenas como log, então um challenge virava um travamento silencioso de 120 segundos sem link. Os challenges obrigatórios em primeiro plano agora são anunciados via JSON-RPC e a TUI imprime o link.
Obrigatório e opcional são coisas diferentes
Um challenge também pode chegar numa troca bem-sucedida com required: false — o serviço está medindo, não impondo. Esse só é anunciado para um cliente desktop (para que uma janela oculta possa liberá-lo cedo); nunca é aberto no seu browser e nada espera por ele.
Um challenge obrigatório de fato faz uma surface interativa esperar: até 120 segundos por tentativa, o que limita o caso de “um humano está resolvendo um captcha” sem parar uma requisição pelos dez minutos completos do ticket. Um anúncio que falha (digamos, um problema na saída padrão) volta para a impressão do link em vez de perdê-lo e falhar a execução.
O que acontece quando ele não libera
- Um challenge que nunca libera termina com texto retentável e um cooldown de fail-fast em vez de retentativas sem fim.
- Um segundo
challenge_requirednão entra em loop — ele aparece como um erro retentável com uma explicação. Isso é proposital: um loop seria pior que uma falha. - Um código de erro
pow_*num status que não seja 428 mantém o seu significado original (ele não é mais tratado como erro de servidor retentável em todos os endpoints), então fluxos de cadastro e promoção não são afetados.
O que fazer a respeito
Se a sua máquina não tem ninguém acompanhando (um servidor, um contêiner, CI):
- abra esse link no seu browser e passe pela verificação; o ticket pertence àquela troca, então deixe o Hermes tentar de novo depois.
- se isso continuar acontecendo, considere rodar trabalho agendado na capacidade de uma conta Nous logada em vez do free tier anônimo — o caminho de conta não passa pela verificação anônima. Veja este texto sobre a rota de busca gratuita para os diagnósticos relacionados.
- No desktop, não faça nada: a janela oculta cuida disso em segundo plano e só aparece se você for necessário.
A versão curta
A verificação não é um bug; é a política do portal fazendo o seu trabalho. As responsabilidades do Hermes são estreitas de propósito: colocar o link na sua frente, nunca bloquear onde ninguém está olhando, e nunca concluir localmente que uma verificação passou. Depois que você conhece essa divisão de trabalho, um link nos seus logs é um conserto de cinco segundos em vez de uma manhã perdida.
Se você preferir não depender do free tier de jeito nenhum, compare cinco canais de busca gratuitos.