vxcontrol/pentagi · warning · RetryableError

%s (HTTP %d)

Error message

%s (HTTP %d)

What it means

ClassifyHTTPStatus returns a RetryableError for any status >= 500, since an upstream/server-side problem may clear on a retry of the same engine. The message embeds the caller-provided text plus the actual HTTP status code.

Source

Thrown at backend/pkg/tools/searchers/errors.go:59

// Fatal wraps err as a FatalError.
func Fatal(err error) error {
	return &FatalError{Err: err}
}

// ClassifyHTTPStatus maps a non-2xx HTTP status to the right typed error so every
// engine classifies identically. The engine supplies a human-readable message; the
// retry/fatal decision is centralized here.
//
//	429       -> RetryableError (rate limited; back off and retry the same engine)
//	5xx       -> RetryableError (upstream problem; may clear on retry)
//	4xx/other -> FatalError     (auth/bad-request/etc.; retrying the same engine is pointless)
func ClassifyHTTPStatus(status int, msg string) error {
	switch {
	case status == http.StatusTooManyRequests:
		return Retryable(fmt.Errorf("%s (HTTP 429)", msg), 0)
	case status >= 500:
		return Retryable(fmt.Errorf("%s (HTTP %d)", msg, status), 0)
	default:
		return Fatal(fmt.Errorf("%s (HTTP %d)", msg, status))
	}
}

// IsRetryable reports whether err (or anything it wraps) is a RetryableError.
func IsRetryable(err error) bool {
	var t *RetryableError
	return errors.As(err, &t)
}

// IsFatal reports whether err (or anything it wraps) is a FatalError.
func IsFatal(err error) bool {
	var t *FatalError
	return errors.As(err, &t)
}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Retry the same engine; the typed error signals the transient nature.
  2. Check the engine provider's status page for an outage.
  3. If 5xx persists, switch to a fallback engine via the fallbackStrategy chain.
  4. Log the status code and msg to identify which upstream endpoint is failing.

Example fix

// before
return classifyGoogleError(status, body)
// after (caller side)
if err := ...; searchers.IsRetryable(err) { retryOrFallback(err) }
Defensive patterns

Strategy: retry

Validate before calling

// health-check the engine before batch runs
resp, err := http.Get(engineBaseURL + "/healthz")
if err != nil || resp.StatusCode >= 500 { skipEngine() }

Type guard

func isServerError(err error) bool {
  return searchers.IsRetryable(err) && !strings.Contains(err.Error(), "HTTP 429")
}

Try / catch

err := searcher.Handle(ctx, q)
if err != nil && searchers.IsRetryable(err) {
  if attempt < maxAttempts { retryWithBackoff() } else { useFallbackEngine() }
}

Prevention

When it happens

Trigger: Upstream search engine returns 500, 502, 503 or similar while ClassifyHTTPStatus is called from an engine adapter (e.g. classifyGoogleError).

Common situations: Engine provider outage or partial degradation, gateway timeouts behind the search API, transient 502s from a CDN in front of the engine.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/43ec57ed6a966313. Report an issue: GitHub.