Skip to content

claude_code: erro da API que chega por ResultError é classificado pelo texto, e o api_error_status não é lido #227

Closed

Description

@bdcdo

Quando a API recusa a chamada, o SDK do Claude Code levanta ResultError de dentro do query(), com o status HTTP no atributo api_error_status. A classificação de erros não lê esse atributo, e o destino da linha passa a depender do texto da mensagem.

from claude_agent_sdk import ResultError

from dataframeit.errors import is_rate_limit_error, is_recoverable_error


def erro(status, texto):
    dados = {"type": "result", "subtype": "error_during_execution", "is_error": True, "api_error_status": status, "result": texto}
    return ResultError(f"Claude Code returned an error result: {texto}", data=dados, exit_code=1)


for status, texto in [
    (401, "Invalid API key · Please run /login"),
    (401, "API Error: 401 authentication_error"),
    (400, "API Error: 400 invalid_request_error"),
    (529, "API Error: Overloaded"),
]:
    e = erro(status, texto)
    print(e.api_error_status, repr(texto), "| recuperável:", is_recoverable_error(e), "| rate limit:", is_rate_limit_error(e))
# 401 'Invalid API key · Please run /login' | recuperável: True | rate limit: False
# 401 'API Error: 401 authentication_error' | recuperável: False | rate limit: False
# 400 'API Error: 400 invalid_request_error' | recuperável: True | rate limit: False
# 529 'API Error: Overloaded' | recuperável: True | rate limit: False

Com claude-agent-sdk 0.2.165. O status só decide quando o número aparece no texto:

  • 401 sem o número no texto é tratado como recuperável, e a chamada se repete até max_retries com uma credencial que não vai passar a valer. Pelo provider, com max_retries=3, foram 3 chamadas a query().
  • 400 é repetido como recuperável.
  • 529 não conta como sobrecarga, e os workers não são reduzidos.
  • O custo da tentativa se perde: o frame de erro informa total_cost_usd, e o cost_usd anexado ao erro fica em 0.

Qual texto o CLI devolve num 401 de verdade não foi conferido, porque exige chamada de rede. As reproduções usam a exceção do SDK montada à mão e o laço de leitura do SDK sobre um transporte falso.

Causa

_own_http_status (errors.py) lê status_code, code e http_status, os nomes de _HTTP_STATUS_ATTRIBUTES. O SDK usa api_error_status.

_raise_for_result_error (claude_code.py), que já classifica o ResultMessage de erro pelo status e separa sobrecarga, falha transitória e falha definitiva, não chega a rodar nesse caminho: a exceção sai do query() antes de o provider ver a mensagem.

Proposta

Duas partes, que podem entrar separadas:

  • Acrescentar api_error_status a _HTTP_STATUS_ATTRIBUTES. Resolve o 401 e o 400 como definitivos e o 429 como rate limit. Ler pelo atributo, e não pela classe, porque o extra aceita claude-agent-sdk>=0.1.48.
  • No provider, capturar a exceção que carrega api_error_status e passá-la pela mesma classificação de _raise_for_result_error, somando o custo da tentativa. Resolve o 529 como sobrecarga e o custo perdido.

A #192 pedia, na proposta, classificar pelo atributo onde o código HTTP vem do SDK; o #203 tratou da resposta recusada, e esta parte ficou para cá. Relacionada: #193 e #194.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions