Under which category would you file this issue?
Airflow Core
Apache Airflow version
3.3.1
What happened and how to reproduce it?
When Airflow is served under a path prefix ([api] base_url = http://host/workflows), the UI
issues its first API request to the origin root instead of the configured prefix:
GET /api/v2/version <- actual
GET /workflows/api/v2/version <- expected
Every later request is correctly prefixed; only the startup one is wrong.
On a deployment where something else is mounted at /api/, that request reaches the wrong
service. In our case a reverse proxy routes /api/ to a different application, which answers
401. The UI treats the 401 as "session expired" and redirects to login; login succeeds, the
app reloads, the same request 401s again. The result is an infinite login redirect loop —
the UI renders its frame, data loads correctly, then it bounces back to login, about once per
second.
Even where /api/ is not otherwise routed, the request is still sent to the wrong path.
This problem is not present in 3.2.0.
Root cause
OpenAPI.BASE is assigned as a module side effect in queryClient.ts:
// airflow-core/src/airflow/ui/src/queryClient.ts
import i18n from "src/i18n/config"; // line 23
...
OpenAPI.BASE = document.querySelector("head>base")?.getAttribute("href") ?? ""; // line 27
But queryClient.ts imports src/i18n/config, and that module requests the version at
module scope:
// airflow-core/src/airflow/ui/src/i18n/config.ts
export const resolveI18nVersion = (): Promise<string> =>
VersionService.getVersion()
.then((data) => data.version)
.catch(() => Date.now().toString());
void resolveI18nVersion().then(initI18n); // line 163
ES module evaluation runs a dependency to completion before the importing module's body, so
i18n/config.ts always issues getVersion() before line 27 assigns OpenAPI.BASE. The
generated client builds the URL as config.BASE + path with BASE still "".
A visible side effect: because the request fails, resolveI18nVersion falls back to
Date.now(), so translation files are fetched with a timestamp cache buster
(?v=1787955819428) rather than the version. The cache-busting feature the call exists for is
therefore also defeated on these deployments.
Regression
Introduced by #65720 ("i18n translation files served stale after Airflow upgrade due to
browser cache"), which added the getVersion() call to i18n/config.ts. That commit is not in
3.2.0 and is in 3.3.0+. In 3.2.0 i18n/config.ts neither imports VersionService nor calls
the API, so the first request happened after OpenAPI.BASE was set.
Note there are now three separate <base href> lookups in the UI
(queryClient.ts, i18n/config.ts, utils/links.ts), which is what let these drift apart.
How to reproduce
Unit test (fails on main):
// airflow-core/src/airflow/ui/src/queryClient.test.ts
const BASE_HREF = "/workflows/";
vi.resetModules();
document.head.innerHTML = `<base href="${BASE_HREF}" />`;
const requested: Array<string> = [];
vi.spyOn(axios, "request").mockImplementation(async (config) => {
requested.push(String(config.url));
return { config, data: { git_version: null, version: "3.3.1" }, headers: {}, status: 200, statusText: "OK" };
});
await import("src/queryClient");
await vi.waitFor(() => expect(requested.length).toBeGreaterThan(0));
expect(requested.filter((url) => !url.startsWith(BASE_HREF))).toEqual([]);
AssertionError: expected [ '/api/v2/version' ] to deeply equal []
What you think should happen instead?
All API requests, including any issued during module initialization, resolve against
<base href> so that a prefix deployment never sends requests to the origin root.
Operating System
macOS 26.6.2
Deployment
Docker-Compose
Apache Airflow Provider(s)
No response
Versions of Apache Airflow Providers
No response
Official Helm Chart version
Not Applicable
Kubernetes Version
No response
Helm Chart configuration
No response
Docker Image customizations
https://github.com/blue-core-lod/bluecore-workflows/blob/upgrade-airflow-3.3.1/Dockerfile
Anything else?
No response
Are you willing to submit PR?
Code of Conduct
Under which category would you file this issue?
Airflow Core
Apache Airflow version
3.3.1
What happened and how to reproduce it?
When Airflow is served under a path prefix (
[api] base_url = http://host/workflows), the UIissues its first API request to the origin root instead of the configured prefix:
Every later request is correctly prefixed; only the startup one is wrong.
On a deployment where something else is mounted at
/api/, that request reaches the wrongservice. In our case a reverse proxy routes
/api/to a different application, which answers401. The UI treats the 401 as "session expired" and redirects to login; login succeeds, theapp reloads, the same request 401s again. The result is an infinite login redirect loop —
the UI renders its frame, data loads correctly, then it bounces back to login, about once per
second.
Even where
/api/is not otherwise routed, the request is still sent to the wrong path.This problem is not present in 3.2.0.
Root cause
OpenAPI.BASEis assigned as a module side effect inqueryClient.ts:But
queryClient.tsimportssrc/i18n/config, and that module requests the version atmodule scope:
ES module evaluation runs a dependency to completion before the importing module's body, so
i18n/config.tsalways issuesgetVersion()before line 27 assignsOpenAPI.BASE. Thegenerated client builds the URL as
config.BASE + pathwithBASEstill"".A visible side effect: because the request fails,
resolveI18nVersionfalls back toDate.now(), so translation files are fetched with a timestamp cache buster(
?v=1787955819428) rather than the version. The cache-busting feature the call exists for istherefore also defeated on these deployments.
Regression
Introduced by #65720 ("i18n translation files served stale after Airflow upgrade due to
browser cache"), which added the
getVersion()call toi18n/config.ts. That commit is not in3.2.0 and is in 3.3.0+. In 3.2.0
i18n/config.tsneither importsVersionServicenor callsthe API, so the first request happened after
OpenAPI.BASEwas set.Note there are now three separate
<base href>lookups in the UI(
queryClient.ts,i18n/config.ts,utils/links.ts), which is what let these drift apart.How to reproduce
Unit test (fails on
main):What you think should happen instead?
All API requests, including any issued during module initialization, resolve against
<base href>so that a prefix deployment never sends requests to the origin root.Operating System
macOS 26.6.2
Deployment
Docker-Compose
Apache Airflow Provider(s)
No response
Versions of Apache Airflow Providers
No response
Official Helm Chart version
Not Applicable
Kubernetes Version
No response
Helm Chart configuration
No response
Docker Image customizations
https://github.com/blue-core-lod/bluecore-workflows/blob/upgrade-airflow-3.3.1/Dockerfile
Anything else?
No response
Are you willing to submit PR?
Code of Conduct