Skip to content

fix(llm_client): map Faraday::Error HTTP failures so RetryHandler can retry them - #61

Open
minerva-sky wants to merge 2 commits into
codenamev:mainfrom
minerva-sky:fix/faraday-error-mapping
Open

minerva-sky wants to merge 2 commits into
codenamev:mainfrom
minerva-sky:fix/faraday-error-mapping

Conversation

@minerva-sky

Copy link
Copy Markdown
Collaborator

Problem

LlmClient#execute_request only rescued OpenAI::Error. ruby-openai 8.x (8.3.0 in the lockfile) re-raises Faraday::Error subclasses for HTTP failures (429 / 401 / 5xx / timeouts) instead of wrapping them, so map_openai_error was unreachable for every real HTTP error. They fell through to a generic LlmError, which is not in RetryHandler's retryable set, so rate limits and server errors were never retried.

Found by inspection of llm_client.rb:163-183 and retry_handler.rb:59-62; quality-loop candidate.

Change

  • Rescue OpenAI::Error, Faraday::Error in #execute_request.
  • New Faraday::Error branch in map_openai_error, keyed on response_status: 429 → LlmRateLimitError (with retry-after), 401/403 → LlmAuthenticationError, 400/404/422 → LlmInvalidRequestError, 5xx → LlmServerError, no status → LlmNetworkError. Existing Faraday::TimeoutError / Faraday::ConnectionFailed branches are untouched and still win.
  • Specs: unit coverage for each status class, plus two integration specs through #complete showing a Faraday 429 surfaces as LlmRateLimitError and is retried to success with fail_on_error: true.

Notes for review

  • Retries only engage when fail_on_error: true, because handle_error returns a failed LlmResponse instead of raising otherwise, so RetryHandler never sees the error. That is pre-existing behaviour and out of scope here; flagging it in case you want a follow-up.
  • bundle exec rake locally: standard clean, 1396 examples, the only failure is the EventDispatcher timing spec (total_time < 0.1, got 0.40 under nice next to Plex), unrelated.

🤖 Generated with Claude Code

minerva-sky and others added 2 commits October 12, 2026 01:04
… retry them

ruby-openai 8.x re-raises Faraday::Error for 429/401/5xx/timeouts instead of
wrapping them in OpenAI::Error, so the rescue in #execute_request never reached
map_openai_error and every HTTP failure became a generic LlmError that
RetryHandler refused to retry. Rescue Faraday::Error alongside OpenAI::Error and
map by response_status (429 → rate limit, 401/403 → auth, 400/404/422 → invalid
request, 5xx → server, otherwise network).

WIP: specs for the new mapping branch land in the next commit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ock indentation

Unit specs for map_openai_error with Faraday::TooManyRequestsError (429 +
retry-after), Unauthorized/Forbidden (401/403), BadRequest/ResourceNotFound/
UnprocessableEntity (400/404/422), ServerError (503), a status-less
Faraday::Error, and Faraday::TimeoutError. Integration specs through
#complete: a Faraday 429 now surfaces as LlmRateLimitError instead of a
generic LlmError, and with fail_on_error: true RetryHandler retries it and
succeeds on the next attempt.

Also re-indents the Faraday::Error branch, which landed flush-left in the
previous commit (standardrb clean).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant