we-promise/sure · error · Assistant::Error
External assistant returned HTTP #{response.code}.
Error message
External assistant returned HTTP #{response.code}. What it means
Raised by Assistant::External::Client#stream_response when the upstream OpenAI-compatible endpoint answers with any non-2xx status. The response code is interpolated into the message and the first 500 bytes of the body are logged as a warning before the raise. Assistant::Error is not in TRANSIENT_ERRORS, so this is never retried.
Source
Thrown at app/models/assistant/external/client.rb:75
sleep(RETRY_DELAY * retries)
retry
end
Rails.logger.error("[External::Client] Unreachable after #{MAX_RETRIES + 1} attempts: #{e.class} - #{e.message}")
raise Assistant::Error, "External assistant is temporarily unavailable."
end
end
private
def stream_response(http, request, &block)
model = nil
buffer = +""
done = false
http.request(request) do |response|
unless response.is_a?(Net::HTTPSuccess)
Rails.logger.warn("[External::Client] Upstream HTTP #{response.code}: #{response.body.to_s.truncate(500)}")
raise Assistant::Error, "External assistant returned HTTP #{response.code}."
end
response.read_body do |chunk|
break if done
buffer << chunk
if buffer.bytesize > MAX_SSE_BUFFER
raise Assistant::Error, "External assistant stream exceeded maximum buffer size."
end
while (line_end = buffer.index("\n"))
line = buffer.slice!(0..line_end).strip
next if line.empty?
next unless line.start_with?("data:")
data = line.delete_prefix("data:")
data = data.delete_prefix(" ") # SSE spec: strip one optional leading space
View on GitHub (pinned to e69894adb9)
Solutions
- Read the warn log line '[External::Client] Upstream HTTP <code>: <body>' — the truncated body names the real cause (invalid api key, model not found, quota exceeded).
- For 401/403, fix the token supplied to Assistant::External::Client.new (request['Authorization'] = "Bearer #{@token}") — re-issue or rotate the credential in env.
- For 404, correct the URL or agent id: the client POSTs to uri.request_uri with model: @agent_id, so both must match the upstream's routes.
- For 429/503, add caller-side backoff and retry — the client treats these as permanent for this request.
Example fix
# before
client = Assistant::External::Client.new(url:, token:)
model = client.chat(messages: msgs) { |c| print c }
# after — surface the upstream status instead of an opaque failure
begin
model = client.chat(messages: msgs) { |c| print c }
rescue Assistant::Error => e
if (m = e.message.match(/HTTP (\d{3})/))
case m[1]
when /40[13]/ then raise "External assistant auth rejected: check token"
when "429", "503" then raise "External assistant busy, retry later"
else raise
end
else
raise
end
end Defensive patterns
Strategy: try-catch
Validate before calling
# Fail fast on obviously bad config before spending a request raise ArgumentError, "token missing" if token.blank? uri = URI(url) raise ArgumentError, "bad URL" unless uri.host.present?
Try / catch
begin
model = client.chat(messages: msgs) { |c| print c }
rescue Assistant::Error => e
code = e.message[/HTTP (\d{3})/, 1]
case code
when "401", "403" then raise "External assistant rejected credentials"
when "429", "503" then raise "External assistant busy — retry later"
else raise
end
end Prevention
- Smoke-test the exact URL + Bearer token with curl before deploying config changes.
- Alert on the '[External::Client] Upstream HTTP' warn log — it carries the status and body snippet needed to diagnose 401/404/429 immediately.
- Treat 429/503 as retryable at the caller; the client itself never retries HTTP-status failures.
When it happens
Trigger: POST to the configured URL returns 401 (wrong/missing Bearer token passed to Client.new(url:, token:)), 404 (wrong URL path or model/@agent_id), 400 (malformed messages payload), 429 (rate limited), or 500/502/503 (upstream crash or maintenance). Any of these hits the `unless response.is_a?(Net::HTTPSuccess)` branch at app/models/assistant/external/client.rb:73-76.
Common situations: Expired or rotated API token in env; pointing the client at a base URL that already includes /v1/chat/completions or misses it; upstream deployed behind a gateway returning 502 during restarts; hitting provider rate limits under load.
Related errors
- External assistant connection was interrupted.
- External assistant is temporarily unavailable.
- fetch_failed
- fetch_failed
- server_error
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/6cdbb7cb8baff78d.
Report an issue: GitHub.