fix(notifications): surface provider API error details in test failure messages

This commit is contained in:
GitHub Actions
2026-03-10 17:30:31 +00:00
parent f3d69b0116
commit 2fc5b10d3d
2 changed files with 39 additions and 3 deletions

View File

@@ -92,7 +92,7 @@ func respondSanitizedProviderError(c *gin.Context, status int, code, category, m
c.JSON(status, response)
}
var providerStatusCodePattern = regexp.MustCompile(`provider returned status\s+(\d{3})`)
var providerStatusCodePattern = regexp.MustCompile(`provider returned status\s+(\d{3})(?::\s*(.+))?`)
func classifyProviderTestFailure(err error) (code string, category string, message string) {
if err == nil {
@@ -107,14 +107,18 @@ func classifyProviderTestFailure(err error) (code string, category string, messa
return "PROVIDER_TEST_URL_INVALID", "validation", "Provider URL is invalid or blocked. Verify the URL and try again"
}
if statusMatch := providerStatusCodePattern.FindStringSubmatch(errText); len(statusMatch) == 2 {
if statusMatch := providerStatusCodePattern.FindStringSubmatch(errText); len(statusMatch) >= 2 {
hint := ""
if len(statusMatch) >= 3 && strings.TrimSpace(statusMatch[2]) != "" {
hint = ": " + strings.TrimSpace(statusMatch[2])
}
switch statusMatch[1] {
case "401", "403":
return "PROVIDER_TEST_AUTH_REJECTED", "dispatch", "Provider rejected authentication. Verify your credentials"
case "404":
return "PROVIDER_TEST_ENDPOINT_NOT_FOUND", "dispatch", "Provider endpoint was not found. Verify the provider URL path"
default:
return "PROVIDER_TEST_REMOTE_REJECTED", "dispatch", fmt.Sprintf("Provider rejected the test request (HTTP %s)", statusMatch[1])
return "PROVIDER_TEST_REMOTE_REJECTED", "dispatch", fmt.Sprintf("Provider rejected the test request (HTTP %s)%s", statusMatch[1], hint)
}
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
crand "crypto/rand"
"encoding/json"
"errors"
"fmt"
"io"
@@ -157,6 +158,9 @@ func (w *HTTPWrapper) Send(ctx context.Context, request HTTPWrapperRequest) (*HT
}
if resp.StatusCode >= http.StatusBadRequest {
if hint := extractProviderErrorHint(body); hint != "" {
return nil, fmt.Errorf("provider returned status %d: %s", resp.StatusCode, hint)
}
return nil, fmt.Errorf("provider returned status %d", resp.StatusCode)
}
@@ -410,6 +414,34 @@ func shouldRetry(resp *http.Response, err error) bool {
return resp.StatusCode >= http.StatusInternalServerError
}
// extractProviderErrorHint attempts to extract a short, human-readable error description
// from a JSON error response body. Only well-known fields are extracted to avoid
// accidentally surfacing sensitive or overlong content from arbitrary providers.
func extractProviderErrorHint(body []byte) string {
if len(body) == 0 {
return ""
}
var errResp map[string]any
if err := json.Unmarshal(body, &errResp); err != nil {
return ""
}
for _, key := range []string{"description", "message", "error", "error_description"} {
v, ok := errResp[key]
if !ok {
continue
}
s, ok := v.(string)
if !ok || strings.TrimSpace(s) == "" {
continue
}
if len(s) > 100 {
s = s[:100] + "..."
}
return strings.TrimSpace(s)
}
return ""
}
func readCappedResponseBody(body io.Reader) ([]byte, error) {
limited := io.LimitReader(body, MaxNotifyResponseBodyBytes+1)
content, err := io.ReadAll(limited)