edsu opened a new issue, #72344:
URL: https://github.com/apache/airflow/issues/72344

   ### 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`:
   
   ```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:
   
   ```ts
   // 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`):
   
   ```ts
   // 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?
   
   - [x] Yes I am willing to submit a PR!
   
   ### Code of Conduct
   
   - [x] I agree to follow this project's [Code of 
Conduct](https://github.com/apache/airflow/blob/main/CODE_OF_CONDUCT.md)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to