From 445b186e3b62ee1a05ceba7e828a83c9758c6f1e Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 28 Jun 2026 22:15:55 +0700 Subject: [PATCH] fix(go): classify transient timeout/5xx in extension health as unknown (grey) --- go_backend/extension_health.go | 64 +++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/go_backend/extension_health.go b/go_backend/extension_health.go index 749a0b8e..cf20b3fa 100644 --- a/go_backend/extension_health.go +++ b/go_backend/extension_health.go @@ -312,6 +312,9 @@ func classifyExtensionHealthBody(body []byte, serviceKey string) (string, string case "degraded", "partial", "warning", "warn": return "degraded", rawStatus case "down", "offline", "error", "failed", "fail", "unhealthy": + if isTransientHealthStatusMessage(string(body)) { + return "unknown", rawStatus + } return "offline", rawStatus default: return "online", rawStatus @@ -352,42 +355,53 @@ func classifyExtensionHealthService(payload map[string]interface{}, serviceKey s rawStatus, hasStatus := service["status"] okValue, hasOK := service["ok"].(bool) + joinedMessage := strings.Join(messageParts, ": ") + transient := isTransientHealthStatusMessage(detail) || + isTransientHealthStatusMessage(errText) || + isTransientHealthStatusMessage(label) + if statusCode, ok := healthNumber(rawStatus); ok { if statusCode >= 200 && statusCode < 300 { - return "online", strings.Join(messageParts, ": "), true + return "online", joinedMessage, true } if statusCode == http.StatusUnauthorized || statusCode == http.StatusForbidden { - return "degraded", strings.Join(messageParts, ": "), true + return "degraded", joinedMessage, true } if statusCode == http.StatusInternalServerError && hasOK && okValue { - return "online", strings.Join(messageParts, ": "), true + return "online", joinedMessage, true } - return "offline", strings.Join(messageParts, ": "), true + if transient || isTransientHealthStatusCode(statusCode) { + return "unknown", joinedMessage, true + } + return "offline", joinedMessage, true } if isExtensionHealthAuthRequired(detail) { - return "degraded", strings.Join(messageParts, ": "), true + return "degraded", joinedMessage, true + } + if transient { + return "unknown", joinedMessage, true } if hasOK { if okValue { - return "online", strings.Join(messageParts, ": "), true + return "online", joinedMessage, true } - return "offline", strings.Join(messageParts, ": "), true + return "offline", joinedMessage, true } if !hasStatus { - return "unknown", strings.Join(messageParts, ": "), true + return "unknown", joinedMessage, true } statusString := strings.ToLower(strings.TrimSpace(fmt.Sprintf("%v", rawStatus))) switch statusString { case "ok", "up", "online", "healthy", "operational": - return "online", strings.Join(messageParts, ": "), true + return "online", joinedMessage, true case "degraded", "partial", "warning", "warn": - return "degraded", strings.Join(messageParts, ": "), true + return "degraded", joinedMessage, true case "down", "offline", "error", "failed", "fail", "unhealthy": - return "offline", strings.Join(messageParts, ": "), true + return "offline", joinedMessage, true default: - return "unknown", strings.Join(messageParts, ": "), true + return "unknown", joinedMessage, true } } @@ -400,6 +414,32 @@ func isExtensionHealthAuthRequired(detail string) bool { } } +func isTransientHealthStatusMessage(text string) bool { + t := strings.ToLower(strings.TrimSpace(text)) + if t == "" { + return false + } + return strings.Contains(t, "context deadline exceeded") || + strings.Contains(t, "deadline exceeded") || + strings.Contains(t, "timeout") || + strings.Contains(t, "timed out") || + strings.Contains(t, "temporarily unavailable") || + strings.Contains(t, "try again") +} + +func isTransientHealthStatusCode(code int) bool { + switch code { + case http.StatusRequestTimeout, + http.StatusTooManyRequests, + http.StatusBadGateway, + http.StatusServiceUnavailable, + http.StatusGatewayTimeout: + return true + default: + return false + } +} + func healthNumber(value interface{}) (int, bool) { switch v := value.(type) { case float64: