Skip to content

fix(responses): map upstream 4xx on cancel to client error instead of 500 - #32271

Merged
mateo-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_fix_responses_cancel_status
Jul 6, 2026
Merged

fix(responses): map upstream 4xx on cancel to client error instead of 500#32271
mateo-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_fix_responses_cancel_status

Conversation

@mateo-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Found while investigating a customer report on Responses API follow-up requests

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Live proxy on localhost:33551 (azure-responses-test -> azure/gpt-4o, api_version 2025-03-01-preview), real Azure OpenAI calls

Before (unmodified staging): cancelling a synchronous response surfaces Azure's 400 as a 500 APIConnectionError

$ RID=$(curl -s -X POST http://localhost:33551/v1/responses \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
    -d '{"model":"azure-responses-test","input":"Say hello in five words"}' | jq -r .id)
$ curl -s -w "\nHTTP %{http_code}\n" -X POST "http://localhost:33551/v1/responses/${RID}/cancel" \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY"
{"error":{"message":"litellm.APIConnectionError: azure - {\n  \"error\": {\n    \"message\": \"Cannot cancel a synchronous response.\",\n    \"type\": \"invalid_request_error\",\n    \"param\": null,\n    \"code\": null\n  }\n}. Received Model Group=08b90bf8d58c60394752c30ca84489fb43ca4ca81fd3673c59ab778ba16a7dd1\nAvailable Model Group Fallbacks=None","type":null,"param":null,"code":"500"}}
HTTP 500

After (this branch): the upstream client error passes through with its real status and message

$ RID=$(curl -s -X POST http://localhost:33551/v1/responses \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
    -d '{"model":"azure-responses-test","input":"Say hello in five words"}' | jq -r .id)
$ curl -s -w "\nHTTP %{http_code}\n" -X POST "http://localhost:33551/v1/responses/${RID}/cancel" \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY"
{"error":{"message":"litellm.BadRequestError: AzureException BadRequestError - {\n  \"error\": {\n    \"message\": \"Cannot cancel a synchronous response.\",\n    \"type\": \"invalid_request_error\",\n    \"param\": null,\n    \"code\": null\n  }\n}. Received Model Group=08b90bf8d58c60394752c30ca84489fb43ca4ca81fd3673c59ab778ba16a7dd1\nAvailable Model Group Fallbacks=None","type":null,"param":null,"code":"400"}}
HTTP 400

Cancelling a background: true response still works (must not regress)

$ RID=$(curl -s -X POST http://localhost:33551/v1/responses \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY" -H "Content-Type: application/json" \
    -d '{"model":"azure-responses-test","input":"Write a 2000 word essay about the history of lighthouses in great detail","background":true}' | jq -r .id)
$ curl -s -o /dev/null -w "HTTP %{http_code}\n" -X POST "http://localhost:33551/v1/responses/${RID}/cancel" \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY"
HTTP 200

with body {"status":"cancelled","background":true,...}

The same passthrough now applies to the other follow-ups; DELETE of an already deleted response returned a 500 before and now returns the upstream 404

$ curl -s -o /dev/null -w "HTTP %{http_code}\n" -X DELETE "http://localhost:33551/v1/responses/${RID}" \
    -H "Authorization: Bearer $LITELLM_MASTER_KEY"
HTTP 404

Type

🐛 Bug Fix

Changes

POST /v1/responses/{id}/cancel against an Azure synchronous (non background) response makes Azure reject with a 400 "Cannot cancel a synchronous response.", but litellm surfaced it as a 500 APIConnectionError. The responses follow-up endpoints (cancel/get/delete, and likewise evals, skills, vector stores, interactions, rag) call litellm.exception_type(model=None, ...), and the whole provider mapping block in exception_type is gated on if model:, so every upstream error on those paths, whatever its real status code, fell through to the generic 500 APIConnectionError fallback

The gate now also enters the mapping when only custom_llm_provider is known, which is what the per-provider mappers actually dispatch on; none of them dereference model beyond message formatting. Upstream 4xx errors on follow-up requests now map to the proper litellm exception (BadRequestError for the sync cancel case) with the provider's original message, exactly like completion-style endpoints already do

Regression test in tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py: an upstream 400 wrapped in a BaseLLMException with model=None and custom_llm_provider="azure" must map to litellm.BadRequestError with status 400 and the upstream message preserved. It fails on the pre-fix code with APIConnectionError

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where Responses API follow-up endpoints (cancel, get, delete) that call exception_type with model=None bypassed the entire provider-specific exception mapping block and always surfaced upstream errors as a generic 500 APIConnectionError. The root cause was the gate condition if model:, which was falsy when only custom_llm_provider was known.

  • The fix widens the gate to if model or custom_llm_provider:, allowing provider-specific mappers (which dispatch only on custom_llm_provider) to run even when model=None. All downstream code handles model=None safely via try/except or by accepting None in exception constructors.
  • A focused mock test is added that reproduces the exact scenario: Azure invalid_request_error with model=None must map to BadRequestError(status_code=400) rather than APIConnectionError.

Confidence Score: 5/5

Safe to merge — the change is a single boolean expression fix with a dedicated regression test and demonstrated live-proxy evidence.

The change is minimal and targeted: one boolean gate touches only the entry condition for the provider-exception-mapping block. All code inside the block (extra_information formatting, provider mappers, fallback APIConnectionError) already handles model=None gracefully. The test adds a direct unit test for the broken path and relies only on mocks. The PR description includes before/after live-proxy output confirming the fix and a successful regression check on the background-cancel case.

No files require special attention.

Important Files Changed

Filename Overview
litellm/litellm_core_utils/exception_mapping_utils.py One-line gate change from if model: to if model or custom_llm_provider: correctly routes follow-up endpoint exceptions (cancel/get/delete) into the provider-specific mapping block instead of falling through to the generic APIConnectionError path.
tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py New mock-only test exercises the fixed code path: BaseLLMException(status_code=400) with model=None and custom_llm_provider="azure" must now raise litellm.BadRequestError with status 400 instead of the previous APIConnectionError.

Reviews (1): Last reviewed commit: "fix(responses): map upstream 4xx on canc..." | Re-trigger Greptile

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a one-character bug in exception_type() where the provider-specific error-mapping block was gated on if model:, causing all follow-up Responses API calls (cancel, get, delete) that pass model=None to fall through to the generic 500 APIConnectionError regardless of the upstream HTTP status.

  • The gate is widened to if model or custom_llm_provider:, sufficient since every per-provider mapper dispatches on custom_llm_provider, not model.
  • A unit test is added covering the exact regression scenario with no real network calls.

Confidence Score: 5/5

Minimal, well-scoped bug fix with a direct regression test and live proof-of-fix screenshots — safe to merge.

Single-character addition to a guard condition; downstream mappers already handle model=None safely via try/except; new test covers the exact regression with no network calls.

No files require special attention.

Important Files Changed

Filename Overview
litellm/litellm_core_utils/exception_mapping_utils.py One-line fix: gates the provider-specific exception-mapping block on model or custom_llm_provider instead of model alone.
tests/test_litellm/litellm_core_utils/test_exception_mapping_utils.py Adds a focused regression test verifying exception_type(model=None, custom_llm_provider='azure', ...) raises litellm.BadRequestError with no network calls.

Reviews (2): Last reviewed commit: "fix(responses): map upstream 4xx on canc..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@mateo-berri
mateo-berri enabled auto-merge (squash) July 6, 2026 20:57
@mateo-berri
mateo-berri merged commit b4a10fb into litellm_internal_staging Jul 6, 2026
123 checks passed
@mateo-berri
mateo-berri deleted the litellm_fix_responses_cancel_status branch July 6, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants