Revertido após 11 horas: por que os prompts de aprovação do Hermes mantêm um prazo


Se você leu ontem que os prompts de aprovação do Hermes agora esperavam por você para sempre, talvez tenha notado que eles voltaram a fazer contagem regressiva e negar quando o tempo acaba. Isso parece um mantenedor mudando de ideia, mas na verdade é uma linha de projeto sendo traçada às pressas: a mudança resolvia um incômodo real e criava um deadlock justamente no único lugar onde ninguém pode responder. Ela foi revertida 11 horas e 32 minutos depois de ser mesclada.

Este post não toma partido. Ele apresenta a linha do tempo, os motivos e — o mais importante — o comportamento que de fato vale agora, porque é isso que decide como você deve configurar approvals.

Qual é o comportamento hoje

Fixe a conclusão primeiro, porque ela determina se você precisa mexer na sua configuração. Em 3 de outubro de 2026:

Superfície Quanto tempo o prompt vive
CLI interativa, TUI, app Desktop Faz contagem regressiva até approvals.timeout (padrão 300 s) e então trata como não aprovado e bloqueia o comando
Telegram / Discord / Slack e outras plataformas de mensagens Também usa approvals.timeout; uma notificação push pode ficar sem ser vista por minutos
Perguntas de esclarecimento (agent.clarify_timeout) Padrão 3600 s (1 hora); ao expirar, a espera é liberada com “user did not respond within Xm”

O contrato central não mudou, e isso importa: um timeout não é aprovação. Não é “ninguém respondeu, então vá em frente” — é “ninguém respondeu, então não execute.” O texto do upstream para um prompt cancelado resume em uma linha: silêncio não é consentimento. Um prazo é o padrão seguro, e o preço é que você pode perder uma operação silenciosamente.

O que fazia a mudança revertida

Às 16:50 CST de 1º de outubro, o PR #130167 foi mesclado. A premissa era separar as superfícies conforme houvesse ou não um humano na frente da tela:

Os prompts de aprovação e de esclarecimento na CLI, na TUI e no app Desktop deixariam de expirar. Ficavam ativos até serem respondidos, até o turno ser interrompido ou até a sessão ser encerrada. Plataformas de mensagens, editores ACP e plugins de transporte de aprovação mantinham approvals.timeout e agent.clarify_timeout, porque uma notificação push realmente pode ficar sem ser vista e os botões da plataforma expiram.

Mecanicamente, era um par de ContextVar por turno — set_prompts_wait_for_answer() / reset_prompts_wait_for_answer() — mais um resolver, approval_wait_seconds(), que devolvia o máximo seguro da plataforma quando o switch estava ativo e approvals.timeout caso contrário. O prompt da CLI, o loop de polling de aprovação do gateway da TUI/Desktop e o teto de espera humana liam esse único ponto de entrada.

Como instinto de produto, é tentador: você levanta para pegar água, volta, o prompt ainda está lá, você não perde a operação.

Por que foi revertida naquela mesma noite

Às 04:23 CST de 2 de outubro, o PR #130826 reverteu tudo. O corpo do revert apresenta três motivos, dois deles bloqueadores sérios.

Primeiro, a direção já havia sido rejeitada antes de o PR ser mesclado. A linha de abertura do revert é direta: “prompts de espera infinita foram rejeitados como direção antes de o #130167 ser mesclado”. Isso é uma falha de processo, não técnica.

Segundo, quebrou o hermes chat -q. Esse foi o que realmente importou. O modo de consulta única não tem supervisão: não há ninguém sentado ali para responder. Remover o prazo de um prompt de aprovação faz a sessão esperar indefinidamente por uma resposta que nunca virá — ela simplesmente travava. O upstream teve de lançar um patch de acompanhamento (82c63c505a9) para devolver o prazo ao chat -q, e este revert leva esse patch junto, porque corrigir o travamento era sua única razão de existir.

Terceiro, um teste end-to-end passou a falhar em toda execução. tests/e2e/core/providers/test_native_codex_app_server_faults.py falhava em toda rodada de e2e na main até que o mesmo patch de acompanhamento cobrisse a única superfície que ele tocava.

A escala do rollback: 11 commits mais o patch de acompanhamento dependente, cobrindo 57 arquivos, cada um idêntico byte a byte ao seu estado pré-#130167 (5 arquivos carregam commits posteriores e não relacionados que permanecem intactos). O que voltou é o comportamento que você vê hoje — os painéis de aprovação da CLI e da TUI/Desktop fazem contagem regressiva e negam no timeout configurado, as solicitações de esclarecimento voltaram a ter prazo, um turno de grupo do Bot Mode bloqueado por um usuário mantém seu teto rígido de 3 horas, e a documentação, os textos de configuração e as strings de localidade foram restaurados.

Vale guardar um detalhe: a mudança nunca chegou a um release. A tag mais recente na época era v2026.9.24 (versão v0.21.5, publicada em 24 de setembro) e o #130167 foi mesclado em 1º de outubro. Só instalações que acompanham a main viram o prompt de espera infinita; quem usa release nunca o viu.

O que essas 11 horas realmente ensinam

Tire a mecânica e três regras continuam úteis.

Um prazo é um mecanismo de segurança, não um defeito de UX. A contagem regressiva carrega uma responsabilidade: quando ninguém confirma, o sistema precisa ser capaz de parar sozinho. Remova isso e “sem supervisão” vira “travado para sempre”, o que é pior que uma negação — uma negação leva o agente a procurar outra rota, enquanto um travamento apenas se acumula. Se os prompts te incomodam, os ajustes corretos são a duração do timeout e o modo de aprovação, não a ausência de timeout.

“Tem alguém que possa responder?” pertence à intenção, não à duração da espera. Uma sessão -q, um cron job e um gatilho de webhook são fundamentalmente diferentes de você sentado em um terminal. O Hermes dá a cada um um switch explícito: approvals.single_query_mode, approvals.cron_mode e approvals.unattended_mode, cada um deny ou approve, todos com padrão deny — porque nessas superfícies, bloquear na hora e deixar o agente contornar o bloqueio é melhor do que esperar o timeout inteiro e falhar fechado.

Não tente resolver aumentando o timeout lá em cima. O limite no código não é decorativo: approvals.timeout é limitado ao máximo seguro da plataforma (agent.deadline.MAX_SAFE_TIMEOUT_S, 365 dias fora do Windows), porque um valor maior estoura time_t dentro de Thread.join / Lock.acquire e derrubava todo batch de ferramentas em paralelo no macOS em testes ao vivo. O texto do aviso também é formatado para humanos — 300 s aparece como “5 minutes”, 90 s como “90 seconds” — então a CLI, o resultado da ferramenta e o card do gateway dizem a mesma coisa.

O que fazer em vez disso

Leia os valores atuais em vez de adivinhar:

hermes config get approvals.timeout
hermes config get approvals.mode

Se 300 s for apertado demais para suas plataformas de mensagens, aumente — não desative:

hermes config set approvals.timeout 1800

Roteie as superfícies sem supervisão pela intenção, para que decidam imediatamente em vez de esperar uma janela que ninguém vai preencher:

hermes config set approvals.single_query_mode approve   # você confia nas suas execuções -q
hermes config set approvals.unattended_mode deny        # webhooks / API devem bloquear

Bloqueie os comandos que nunca deveriam rodar, com uma deny list — é uma lista de globs fnmatch sem distinção de maiúsculas/minúsculas, e uma correspondência é bloqueada até no modo yolo:

approvals:
  deny:
    - "rm -rf *"
    - "DROP DATABASE *"

Para a semântica completa e uma forma de testar suas regras em dry-run, veja nosso guia de aprovações sem supervisão e o passo a passo de dry-run de aprovações. Se você usa o modo smart padrão, approvals.smart_policy anexa suas próprias regras ao system prompt do revisor.

A conclusão

Em uma linha: este revert não é uma regressão — é um problema legítimo entregue ao mecanismo errado. Um timeout de aprovação é uma válvula de segurança. Se os prompts te incomodam, ajuste a duração e o modo; não remova a válvula.

Uma correção nossa: o post de ontem, prompts de aprovação que esperam até serem respondidos, descrevia o comportamento do #130167 na época. Adicionamos uma nota de atualização a ele. O upstream reverter a direção em menos de um dia é o desfecho que aquele post deveria ter previsto e não previu — e é exatamente por isso que tentamos não tratar “acabou de ser mesclado” como “decisão final”.