
Meu Wrapper `claude -p` Captura Timeouts. Uma Saída Não Zero Não é um Timeout, Então Ele Apenas Falha.
Dois dias atrás, publiquei um post sobre uma convenção de ERRO: que eu havia criado no código de chamada de IA deste repositório: cada caminho de falha de _claude() — o helper que meu servidor MCP e meu script de mensagem de commit usam para chamar claude -p — deveria retornar uma string começando com ERRO:, para que os chamadores pudessem fazer result.startswith("ERRO:") em vez de confiar na saída como uma mensagem de commit real. Esse post corrigiu o único lugar onde a convenção não estava realmente sendo seguida: o ramo de timeout retornava uma string simples sem prefixo.
Eu fechei aquele post tratando a convenção como concluída. Não estava. Há um terceiro caminho de falha na mesma função exata, e ele não retorna uma string — ele levanta uma exceção.
o código como estava
server.py's _claude(), após a correção de segunda-feira:
def _claude(prompt: str, system: str = None) -> str:
full = (system + "\n\n" + prompt) if system else prompt
try:
raw = subprocess.check_output(["claude", "-p", full], text=True, timeout=20).strip()
except subprocess.TimeoutExpired:
return "ERRO: claude -p excedeu o tempo limite após 20s"
return "\n".join(
l for l in raw.splitlines()
if not _STRIP_RE.search(l)
).strip()
Uma cláusula except. subprocess.check_output não apenas excede o tempo limite, no entanto — ele também levanta subprocess.CalledProcessError se o processo filho sair com um status diferente de zero, e FileNotFoundError se o binário não estiver no PATH de forma alguma. Nenhum desses é um TimeoutExpired. Nenhum é capturado. Ambos se propagam diretamente de _claude(), para fora da ferramenta generate_commit_message do MCP, e até o que quer que esteja segurando a chamada da ferramenta.
Eu estava tratando "a convenção tem dois caminhos de falha, certifique-se de que eles concordem" como o problema inteiro. Tem três, e o terceiro não é uma discordância sobre formatação — é a total ausência de qualquer string.
provando isso, não apenas lendo
Eu não queria confiar apenas na palavra do meu próprio código sobre o que subprocess.check_output faz sob uma falha real de claude -p, então eu construí uma. Um binário substituto de uma linha no PATH antes do verdadeiro claude:
#!/bin/sh
echo "falha simulada: taxa limitada" >&2
exit 1
Então eu chamei o caminho de diff em estágio real através de git_commit.py (mesma chamada de subprocesso, mesma except subprocess.TimeoutExpired lacuna) com um arquivo em estágio real:
$ python3 git_commit.py
falha simulada: taxa limitada
Rastreamento (última chamada mais recente):
Arquivo "git_commit.py", linha 36, em <module>
raw = subprocess.check_output(
...
subprocess.CalledProcessError: O comando '['claude', '-p', ...]' retornou status de saída diferente de zero 1.
Rastreamento completo, código de saída 1, nenhuma mensagem limpa em lugar nenhum. Eu fiz a mesma coisa diretamente no server.py's _claude() (substituindo a importação mcp para que eu pudesse carregar o módulo sem o pacote real instalado) e obtive a mesma exceção não capturada. Então eu esvaziei o PATH de qualquer coisa chamada claude completamente e obtive FileNotFoundError da mesma forma — mesma lacuna, gatilho diferente.
A versão do hook de commit disso é silenciosamente salva por um acidente, não por uma decisão de design: hooks/prepare-commit-msg executa git_commit.py com 2>/dev/null e só escreve no arquivo de mensagem de commit se a stdout capturada não estiver vazia:
MSG="$(python "$SCRIPT" 2>/dev/null)" && [ -n "$MSG" ] && printf '%s\n' "$MSG" > "$1"
O rastreamento vai para stderr, que é descartado; stdout está vazio; o hook volta ao template padrão do git. Esse é o resultado certo, mas é o resultado certo pela razão errada — funciona porque o hook acaba descartando stderr, não porque git_commit.py lida com a falha. Execute o script diretamente, fora do hook, e você obtém um rastreamento Python bruto onde uma mensagem de "claude falhou, aqui está o porquê" deveria estar. A ferramenta MCP não tem um wrapper que descarta stderr — uma exceção não capturada lá é apenas uma exceção não capturada.
a correção
Mesma forma que o ramo de timeout, estendida para cobrir as outras duas maneiras subprocess.check_output...
A implementação correta de protocolos de comunicação com agentes de IA é crucial para evitar falhas em sistemas automatizados. Empresas brasileiras que utilizam ferramentas de IA devem garantir que suas integrações tratem adequadamente erros e exceções para manter a confiabilidade dos serviços.
