diff --git a/backend/internal/handler/openai_gateway_credential_failover_test.go b/backend/internal/handler/openai_gateway_credential_failover_test.go
index 2540bf2303..91a3881dfa 100644
--- a/backend/internal/handler/openai_gateway_credential_failover_test.go
+++ b/backend/internal/handler/openai_gateway_credential_failover_test.go
@@ -133,7 +133,8 @@ func TestResponsesFailoverExhaustedAfterForwardedTerminalMarksOpsWithoutDuplicat
recorder := httptest.NewRecorder()
c, _ := gin.CreateTestContext(recorder)
official := "event: response.failed\ndata: {\"type\":\"response.failed\",\"response\":{\"status\":\"failed\",\"error\":{\"code\":\"server_error\",\"message\":\"official failure\"}}}\n\n"
- _, _ = recorder.Write([]byte(official))
+ _, err := c.Writer.Write([]byte(official))
+ require.NoError(t, err)
service.MarkOpsStreamError(c, "server_error", "official failure", http.StatusBadGateway)
(&GatewayHandler{}).handleResponsesFailoverExhausted(c, &service.UpstreamFailoverError{
diff --git a/backend/internal/service/openai_gateway_grok.go b/backend/internal/service/openai_gateway_grok.go
index cc4709443a..9e2c70579b 100644
--- a/backend/internal/service/openai_gateway_grok.go
+++ b/backend/internal/service/openai_gateway_grok.go
@@ -984,9 +984,7 @@ func sanitizeGrokResponsesTools(body []byte) ([]byte, error) {
return deleteGrokOrphanToolControls(body)
}
if !tools.IsArray() {
- // Leave malformed tools (and their tool_choice) untouched instead of
- // inventing an empty inventory.
- return body, nil
+ return deleteGrokOrphanToolControls(body)
}
rawTools := tools.Array()
diff --git a/backend/internal/service/openai_gateway_grok_chat_bridge_test.go b/backend/internal/service/openai_gateway_grok_chat_bridge_test.go
index 373907f0d7..2df7bb1f8b 100644
--- a/backend/internal/service/openai_gateway_grok_chat_bridge_test.go
+++ b/backend/internal/service/openai_gateway_grok_chat_bridge_test.go
@@ -481,7 +481,7 @@ func TestForwardGrokChatViaResponsesTraeCompatibilityFieldsKeepCacheRoute(t *tes
require.Equal(t, "priority", gjson.GetBytes(upstream.lastBody, "service_tier").String())
require.False(t, gjson.GetBytes(upstream.lastBody, "stop").Exists())
require.False(t, gjson.GetBytes(upstream.lastBody, "reasoning").Exists())
- require.Contains(t, gjson.GetBytes(upstream.lastBody, "input.1.content.0.text").String(), "I should use lookup")
+ require.Contains(t, gjson.GetBytes(upstream.lastBody, "input.1.content").String(), "I should use lookup")
require.Equal(t, "function_call", gjson.GetBytes(upstream.lastBody, "input.2.type").String())
require.Equal(t, "function_call_output", gjson.GetBytes(upstream.lastBody, "input.3.type").String())
diff --git a/backend/internal/service/openai_gateway_grok_test.go b/backend/internal/service/openai_gateway_grok_test.go
index 3c93776d4b..f248cf1951 100644
--- a/backend/internal/service/openai_gateway_grok_test.go
+++ b/backend/internal/service/openai_gateway_grok_test.go
@@ -418,10 +418,9 @@ func TestSanitizeGrokResponsesToolsKeepsToolChoiceOnlyWithSupportedTools(t *test
wantToolChoice: true,
},
{
- name: "malformed non-array tools remain untouched",
- body: `{"input":"hello","tools":{"type":"function","name":"lookup"},"tool_choice":"auto"}`,
- wantTools: true,
- wantToolChoice: true,
+ name: "malformed non-array tools drop orphan controls",
+ body: `{"input":"hello","tools":{"type":"function","name":"lookup"},"tool_choice":"auto"}`,
+ wantTools: true,
},
}
diff --git a/backend/internal/service/openai_gateway_passthrough.go b/backend/internal/service/openai_gateway_passthrough.go
index 6aad83b268..40931138d4 100644
--- a/backend/internal/service/openai_gateway_passthrough.go
+++ b/backend/internal/service/openai_gateway_passthrough.go
@@ -1491,7 +1491,15 @@ func openAIStreamErrorEventShouldFailover(payload []byte, message string) bool {
case http.StatusUnauthorized, http.StatusTooManyRequests, 529:
return true
}
- return isOpenAITransientProcessingError(http.StatusBadRequest, message, payload)
+ if isOpenAITransientProcessingError(http.StatusBadRequest, message, payload) {
+ return true
+ }
+ combined := strings.ToLower(strings.TrimSpace(message + " " +
+ gjson.GetBytes(payload, "error.message").String() + " " +
+ gjson.GetBytes(payload, "response.error.message").String()))
+ return strings.Contains(combined, "temporary") ||
+ strings.Contains(combined, "try again") ||
+ strings.Contains(combined, "please retry")
}
func (s *OpenAIGatewayService) handleOpenAIStreamTerminalAccountSideEffects(