Repository navigation
Accept header defaults to */* for single-content-type responses (regression since v5.33.0 / PR #11019) #11070
Description
Q&A (please complete the following information)
- OS: N/A — not browser/OS-specific, this is a request-building logic bug
- Browser: N/A (see above)
- Version: N/A
- Method of installation: npm dist assets, consumed transitively via
darkaonline/l5-swagger(PHP), which bundlesswagger-api/swagger-uias a Composer/npm-mirrored dependency - Swagger-UI version: v5.33.0 (broken) / v5.32.6 (last known-good)
- Swagger/OpenAPI version: OpenAPI 3.0
Content & configuration
Example OpenAPI definition (minimal repro shape — the key point is a response with exactly one content media type):
openapi: "3.0.0"
info:
title: repro
version: "1.0"
paths:
/ping:
get:
responses:
"200":
description: OK
content:
application/json:
schema:
type: objectSwagger-UI configuration options: default, nothing special (no requestInterceptor, no custom responseInterceptor).
Describe the bug you're encountering
For an OAS3 operation whose response declares exactly one content media type (e.g. only application/json), clicking "Try it out" → Execute sends Accept: */* instead of Accept: application/json. This worked correctly through v5.32.6, and broke starting exactly at v5.33.0.
To reproduce...
- Load an OAS3 spec where an operation's response declares a single content type (see YAML above).
- Open that operation in Swagger UI. Note: the "Media type"
<select>under each response code is present and correctly displays the single declared type (e.g.application/json) as selected — do not rely on this display value as evidence of correctness, see below. - Click "Try it out" → "Execute" without changing anything.
- Inspect the outgoing request's
Acceptheader — either via browser devtools Network tab, or more simply via the "Curl" snippet Swagger UI displays under the response after execution: it's generated from the actual resolved request (specSelectors.requestFor/mutatedRequestForinlive-response.jsx, not a separate live-preview), so unlike the "Media type"<select>it reliably reflects what was really sent — it's the easiest way to confirm this bug without opening devtools.
Expected behavior
Swagger UI should send Accept: application/json — the sole content type declared for the response — as it did through v5.32.6. Instead, starting at v5.33.0, it sends Accept: */*.
Screenshots
Not included
Additional context or thoughts
Root cause (traced in source): the Accept header is still built from the legacy, per-operation selector, unchanged since well before this regression:
// src/core/plugins/spec/actions.js (identical in both v5.29.1 and v5.33.0)
req.responseContentType = oas3Selectors.responseContentType(pathName, method) || "*/*"oas3Selectors.responseContentType(path, method) reads requestData[path][method].responseContentType, which is only ever written by the UPDATE_RESPONSE_CONTENT_TYPE action (dispatched from responses.jsx's onResponseContentTypeChange, gated on controlsAcceptHeader).
PR #11019 ("add virtualization for schemas and operations", commit 0ad38e9c, released in v5.33.0) introduced a new, parallel, per-response-code content-type tracking system alongside the old one:
- New action:
UPDATE_RESPONSE_CODE_CONTENT_TYPE/setResponseCodeContentType(src/core/plugins/oas3/actions.js) - New reducer slice:
requestData[path][method].responseCodeContentType[code](src/core/plugins/oas3/reducers.js) - New selector:
responseCodeContentType(path, method, code)(src/core/plugins/oas3/selectors.js)
And refactored src/core/components/response.jsx's _onContentTypeChange/getActiveContentType so that, for OAS3 specs, the dropdown's displayed/tracked value now primarily flows through this new per-code state:
// v5.33.0, src/core/components/response.jsx
_onContentTypeChange = (value) => {
const { onContentTypeChange, controlsAcceptHeader, specSelectors, path, method, code, oas3Actions } = this.props
if (specSelectors.isOAS3()) {
oas3Actions.setResponseCodeContentType({ value, path, method, code }) // NEW slice
} else {
this.setState({ responseContentType: value })
}
onContentTypeChange({ value, controlsAcceptHeader }) // old callback, unchanged
}The component that decides what's displayed was migrated to the new per-code model, but spec/actions.js's Execute logic — the code that actually builds the Accept header — was never updated to read from it. The two selectors have drifted apart, and the one actually used for the request header ends up unpopulated far more easily under the new component flow than before the refactor, producing the */* fallback even in the single-media-type case that previously worked out of the box with zero user interaction.
Suggested fix: in src/core/plugins/spec/actions.js, have the Accept header resolution fall back through both selectors, e.g.:
req.responseContentType =
oas3Selectors.responseCodeContentType(pathName, method, /* current code */) ||
oas3Selectors.responseContentType(pathName, method) ||
"*/*"(or equivalently, have onResponseContentTypeChange keep writing to both slices so they can't drift apart.)
Regression range (bisected by checking UPDATE_RESPONSE_CODE_CONTENT_TYPE's presence in src/core/plugins/oas3/actions.js at every intervening tag):
v5.29.1, v5.30.0-3, v5.31.0-2, v5.32.0-6: absent — Accept header defaults correctly (11 releases checked individually)
v5.33.0: present — Accept header defaults to */*
Workaround: pin to swagger-api/swagger-ui@5.32.6 until fixed.
Activity
UmeshCode1 commentedon Sep 29, 2026
Excellent root cause analysis @MikusMartinsons.
To expand on why this specifically broke when virtualization was introduced in #11019:
The Lifecycle Disconnect
In Swagger UI, an HTTP client cannot know what HTTP status code the server will ultimately return, so the
Acceptheader is governed by the "accept controlling response" (the first2xxordefaultresponse defined in the spec, as determined bygetAcceptControllingResponse(responses)).Before virtualization was introduced in #11019:
ResponsesandResponsecomponents always mounted immediately for the operation.onResponseContentTypeChange, writing the default media type intostate.getIn(["requestData", path, method, "responseContentType"]).<Response>only dispatches_onContentTypeChangewhen a user manually interacts with the<ContentType>dropdown.requestData[path][method].responseContentTyperemainsundefined.When
executeinsrc/core/plugins/spec/actions.jsexecutes:Because the user-interaction slice is empty, it falls straight through to
*/*.Recommended Fix: Spec-Derived Default Fallback
Rather than attempting to synchronize component mount lifecycle events across virtualized views (which re-introduces rendering overhead), the resolution should be declarative and spec-derived.
In
src/core/plugins/spec/actions.js(around line 426) or inoas3Selectors.responseContentType:Why this is resilient:
componentDidMountoronChange) firing in<Response>. Even if the responses table is completely unmounted or virtualized off-screen,executeresolves the correct default media type (application/json, etc.) directly from the immutable spec tree.oas3Selectors.responseContentType(pathName, method)is populated and takes precedence.fix(deps): reverter swagger-ui para 5.32.15 por regressão no 5.33.x