we-promise/sure · error · Assistant::Error

External assistant is temporarily unavailable.

Error message

External assistant is temporarily unavailable.

What it means

Raised by Assistant::External::Client#chat when a transient network error occurs BEFORE any content chunk streamed AND the built-in retry budget is exhausted. The client retries transient failures up to MAX_RETRIES = 2 extra attempts with sleep(RETRY_DELAY * retries) backoff (1s then 2s), so this error means 3 total attempts all failed at connect/first-read time.

Source

Thrown at app/models/assistant/external/client.rb:61

      model = stream_response(http, request) do |content|
        streaming_started = true
        block.call(content)
      end
      model
    rescue *TRANSIENT_ERRORS => e
      if streaming_started
        Rails.logger.warn("[External::Client] Stream interrupted: #{e.class} - #{e.message}")
        raise Assistant::Error, "External assistant connection was interrupted."
      end

      retries += 1
      if retries <= MAX_RETRIES
        Rails.logger.warn("[External::Client] Transient error (attempt #{retries}/#{MAX_RETRIES}): #{e.class} - #{e.message}")
        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

View on GitHub (pinned to e69894adb9)

Solutions

  1. Verify the upstream is reachable from the app host: curl -v <same URL> — fix the stopped service or the URL/env configuration.
  2. Check DNS and egress rules (security groups, firewall) for the app server, and inspect HTTP_PROXY/HTTPS_PROXY/NO_PROXY env vars the client honors.
  3. Retry from the caller with backoff — this failure is safe to retry because nothing was streamed and no side effects occurred.
  4. If the endpoint is flaky by nature, wrap calls in a circuit breaker so you fail fast instead of burning the full retry ladder on every request.

Example fix

# before
result = client.chat(messages: messages) { |chunk| stream.write(chunk) }

# after — safe outer retry because nothing streamed
attempts = 0
begin
  result = client.chat(messages: messages) { |chunk| stream.write(chunk) }
rescue Assistant::Error => e
  raise if e.message.exclude?("temporarily unavailable") || (attempts += 1) >= 3
  sleep(5 * attempts)
  retry
end
Defensive patterns

Strategy: retry

Validate before calling

# Cheap reachability check before the call (nothing has streamed, retry is safe)
require "socket"
require "uri"
uri = URI(external_assistant_url)
Socket.tcp(uri.host, uri.port, connect_timeout: 3).close

Try / catch

attempts = 0
begin
  model = client.chat(messages: messages) { |c| stream.write(c) }
rescue Assistant::Error => e
  raise unless e.message.include?("temporarily unavailable") && attempts < 3
  attempts += 1
  sleep(2**attempts)
  retry
end

Prevention

When it happens

Trigger: client.chat raises Net::OpenTimeout, Errno::ECONNREFUSED, SocketError (bad DNS), EHOSTUNREACH, or ReadTimeout before the first SSE chunk: upstream process down, wrong hostname in @url, port closed, DNS failure, or a proxy in HTTP_PROXY/HTTPS_PROXY refusing the CONNECT. streaming_started stays false through all 3 attempts, falling through to the raise at app/models/assistant/external/client.rb:60-61.

Common situations: External agent service stopped or crashed; wrong EXTERNAL_ASSISTANT URL in env config; DNS not resolving in the deploy environment; a corporate proxy env var pointing at a dead proxy; firewall/security group blocking egress on the target port.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/a772f3faaac65690. Report an issue: GitHub.