diff --git a/app/assets/javascripts/ide/components/oauth_domain_mismatch_error.vue b/app/assets/javascripts/ide/components/oauth_domain_mismatch_error.vue
index 9899e941fbfe9b6342e527340655acf6b9a203bb..24dfba585782079af0eca204d16dc9df7052ea6a 100644
--- a/app/assets/javascripts/ide/components/oauth_domain_mismatch_error.vue
+++ b/app/assets/javascripts/ide/components/oauth_domain_mismatch_error.vue
@@ -1,41 +1,38 @@
 <script>
-import { GlButton, GlSprintf, GlCollapsibleListbox, GlIcon } from '@gitlab/ui';
+import { GlButton, GlSprintf, GlDisclosureDropdown } from '@gitlab/ui';
 import GITLAB_LOGO_SVG_URL from '@gitlab/svgs/dist/illustrations/gitlab_logo.svg?url';
 import { s__ } from '~/locale';
-import { logError } from '~/lib/logger';
+import { joinPaths, stripRelativeUrlRootFromPath } from '~/lib/utils/url_utility';
 
 export default {
   name: 'OAuthDomainMismatchError',
   components: {
     GlButton,
     GlSprintf,
-    GlCollapsibleListbox,
-    GlIcon,
+    GlDisclosureDropdown,
   },
   props: {
-    callbackUrlOrigins: {
+    expectedCallbackUrl: {
+      type: String,
+      required: true,
+    },
+    callbackUrls: {
       type: Array,
       required: true,
     },
   },
   computed: {
     dropdownItems() {
-      return this.callbackUrlOrigins.map((domain) => {
-        return {
-          value: domain,
-          text: domain,
-        };
-      });
-    },
-  },
-  methods: {
-    reloadPage(urlDomain) {
-      try {
-        const current = new URL(urlDomain + window.location.pathname);
-        window.location.replace(current.toString());
-      } catch (e) {
-        logError(s__('IDE|Error reloading page'), e);
-      }
+      const currentOrigin = window.location.origin;
+
+      return this.callbackUrls
+        .filter(({ base }) => new URL(base).origin !== currentOrigin)
+        .map(({ base }) => {
+          return {
+            href: joinPaths(base, stripRelativeUrlRootFromPath(window.location.pathname)),
+            text: base,
+          };
+        });
     },
   },
   gitlabLogo: GITLAB_LOGO_SVG_URL,
@@ -50,6 +47,7 @@ export default {
     description: s__(
       "IDE|The URL you're using to access the Web IDE and the configured OAuth callback URL do not match. This issue often occurs when you're using a proxy.",
     ),
+    expected: s__('IDE|Could not find a callback URL entry for %{expectedCallbackUrl}.'),
     contact: s__(
       'IDE|Contact your administrator or try to open the Web IDE again with another domain.',
     ),
@@ -64,27 +62,28 @@ export default {
       <p>
         {{ $options.i18n.description }}
       </p>
+      <gl-sprintf :message="$options.i18n.expected">
+        <template #expectedCallbackUrl>
+          <code>{{ expectedCallbackUrl }}</code>
+        </template>
+      </gl-sprintf>
       <p>
         {{ $options.i18n.contact }}
       </p>
       <div class="gl-mt-6">
-        <gl-collapsible-listbox
-          v-if="callbackUrlOrigins.length > 1"
+        <gl-disclosure-dropdown
+          v-if="dropdownItems.length > 1"
           :items="dropdownItems"
-          :header-text="$options.i18n.dropdownHeader"
-          @select="reloadPage"
+          :toggle-text="$options.i18n.buttonText.domains"
+        />
+        <gl-button
+          v-else-if="dropdownItems.length === 1"
+          variant="confirm"
+          :href="dropdownItems[0].href"
         >
-          <template #toggle>
-            <gl-button variant="confirm" class="self-center">
-              {{ $options.i18n.buttonText.domains }}
-              <gl-icon class="dropdown-chevron gl-ml-2" name="chevron-down" />
-            </gl-button>
-          </template>
-        </gl-collapsible-listbox>
-        <gl-button v-else variant="confirm" @click="reloadPage(callbackUrlOrigins[0])">
           <gl-sprintf :message="$options.i18n.buttonText.singleDomain">
             <template #domain>
-              {{ callbackUrlOrigins[0] }}
+              {{ dropdownItems[0].text }}
             </template>
           </gl-sprintf>
         </gl-button>
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index f754de6727c13fd8af14ef286aa71d89bbdcc216..c92fe21068d50c0576eba832829955ac9c37f0f6 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -112,3 +112,7 @@ export const DEFAULT_BRANCH = 'main';
 export const GITLAB_WEB_IDE_FEEDBACK_ISSUE = 'https://gitlab.com/gitlab-org/gitlab/-/issues/377367';
 
 export const IDE_ELEMENT_ID = 'ide';
+
+// note: This path comes from `config/routes.rb`
+export const IDE_PATH = '/-/ide';
+export const WEB_IDE_OAUTH_CALLBACK_URL_PATH = '/-/ide/oauth_redirect';
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index 020f8e98da19dad9d81837cef9e200b1b4a69c27..4b856550f2b943cfdf1c8e1c0e9fc304a7917ab1 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -24,24 +24,21 @@ export async function startIde(options) {
     return;
   }
 
-  const oAuthCallbackDomainMismatchApp = new OAuthCallbackDomainMismatchErrorApp(
-    ideElement,
-    ideElement.dataset.callbackUrls,
-  );
-
-  if (oAuthCallbackDomainMismatchApp.isVisitingFromNonRegisteredOrigin()) {
-    oAuthCallbackDomainMismatchApp.renderError();
-    return;
-  }
-
   const useNewWebIde = parseBoolean(ideElement.dataset.useNewWebIde);
 
-  if (useNewWebIde) {
-    const { initGitlabWebIDE } = await import('./init_gitlab_web_ide');
-    initGitlabWebIDE(ideElement);
-  } else {
+  if (!useNewWebIde) {
     resetServiceWorkersPublicPath();
     const { initLegacyWebIDE } = await import('./init_legacy_web_ide');
     initLegacyWebIDE(ideElement, options);
+    return;
+  }
+
+  const oAuthCallbackDomainMismatchApp = new OAuthCallbackDomainMismatchErrorApp(ideElement);
+
+  if (oAuthCallbackDomainMismatchApp.shouldRenderError()) {
+    oAuthCallbackDomainMismatchApp.renderError();
+    return;
   }
+  const { initGitlabWebIDE } = await import('./init_gitlab_web_ide');
+  initGitlabWebIDE(ideElement);
 }
diff --git a/app/assets/javascripts/ide/lib/gitlab_web_ide/get_base_config.js b/app/assets/javascripts/ide/lib/gitlab_web_ide/get_base_config.js
index e131fb669ead710aa239cd3753135b55cc34608e..76a79fddf23760b35dc6f20308c634729ffacab2 100644
--- a/app/assets/javascripts/ide/lib/gitlab_web_ide/get_base_config.js
+++ b/app/assets/javascripts/ide/lib/gitlab_web_ide/get_base_config.js
@@ -10,6 +10,6 @@ const getGitLabUrl = (gitlabPath = '') => {
 export const getBaseConfig = () => ({
   // baseUrl - The URL which hosts the Web IDE static web assets
   baseUrl: getGitLabUrl(process.env.GITLAB_WEB_IDE_PUBLIC_PATH),
-  // baseUrl - The URL for the GitLab instance
+  // gitlabUrl - The URL for the GitLab instance
   gitlabUrl: getGitLabUrl(''),
 });
diff --git a/app/assets/javascripts/ide/lib/gitlab_web_ide/get_oauth_config.js b/app/assets/javascripts/ide/lib/gitlab_web_ide/get_oauth_config.js
index bcd3d240c8cb1c429c5a7665d844227eae1a484e..66a36896a31628cd2f7de64ade67e0149f258218 100644
--- a/app/assets/javascripts/ide/lib/gitlab_web_ide/get_oauth_config.js
+++ b/app/assets/javascripts/ide/lib/gitlab_web_ide/get_oauth_config.js
@@ -1,4 +1,4 @@
-export const WEB_IDE_OAUTH_CALLBACK_URL_PATH = '/-/ide/oauth_redirect';
+import { getOAuthCallbackUrl } from './oauth_callback_urls';
 
 export const getOAuthConfig = ({ clientId }) => {
   if (!clientId) {
@@ -8,7 +8,7 @@ export const getOAuthConfig = ({ clientId }) => {
   return {
     type: 'oauth',
     clientId,
-    callbackUrl: new URL(WEB_IDE_OAUTH_CALLBACK_URL_PATH, window.location.origin).toString(),
+    callbackUrl: getOAuthCallbackUrl(),
     protectRefreshToken: true,
   };
 };
diff --git a/app/assets/javascripts/ide/lib/gitlab_web_ide/oauth_callback_urls.js b/app/assets/javascripts/ide/lib/gitlab_web_ide/oauth_callback_urls.js
new file mode 100644
index 0000000000000000000000000000000000000000..0ea0b83564f0a96760d0298b5399803ee5e98f52
--- /dev/null
+++ b/app/assets/javascripts/ide/lib/gitlab_web_ide/oauth_callback_urls.js
@@ -0,0 +1,75 @@
+import { joinPaths } from '~/lib/utils/url_utility';
+import { logError } from '~/lib/logger';
+import { WEB_IDE_OAUTH_CALLBACK_URL_PATH, IDE_PATH } from '../../constants';
+
+/**
+ * @returns callback URL constructed from current window url
+ */
+export function getOAuthCallbackUrl() {
+  const url = window.location.href;
+
+  // We don't rely on `gon.gitlab_url` and `gon.relative_url_root` here because these may not be configured correctly
+  // or we're visiting the instance through a proxy.
+  // Instead, we split on the `/-/ide` in the `href` and use the first part as the base URL.
+  const baseUrl = url.split(IDE_PATH, 2)[0];
+  const callbackUrl = joinPaths(baseUrl, WEB_IDE_OAUTH_CALLBACK_URL_PATH);
+
+  return callbackUrl;
+}
+
+const parseCallbackUrl = (urlStr) => {
+  let callbackUrl;
+
+  try {
+    callbackUrl = new URL(urlStr);
+  } catch {
+    // Not a valid URL. Nothing to do here.
+    return undefined;
+  }
+
+  // If we're an unexpected callback URL
+  if (!callbackUrl.pathname.endsWith(WEB_IDE_OAUTH_CALLBACK_URL_PATH)) {
+    return {
+      base: joinPaths(callbackUrl.origin, '/'),
+      url: urlStr,
+    };
+  }
+
+  // Else, trim the expected bit to get the origin + relative_url_root
+  const callbackRelativePath = callbackUrl.pathname.substring(
+    0,
+    callbackUrl.pathname.length - WEB_IDE_OAUTH_CALLBACK_URL_PATH.length,
+  );
+  const baseUrl = new URL(callbackUrl);
+  baseUrl.pathname = callbackRelativePath;
+  baseUrl.hash = '';
+  baseUrl.search = '';
+
+  return {
+    base: joinPaths(baseUrl.toString(), '/'),
+    url: urlStr,
+  };
+};
+
+export const parseCallbackUrls = (callbackUrlsJson) => {
+  if (!callbackUrlsJson) {
+    return [];
+  }
+
+  let urls;
+
+  try {
+    urls = JSON.parse(callbackUrlsJson);
+  } catch {
+    // why: We dont want to translate console errors
+    // eslint-disable-next-line @gitlab/require-i18n-strings
+    logError('Failed to parse callback URLs JSON');
+    return [];
+  }
+
+  if (!urls || !Array.isArray(urls)) {
+    return [];
+  }
+
+  return urls.map(parseCallbackUrl).filter(Boolean);
+};
diff --git a/app/assets/javascripts/ide/oauth_callback_domain_mismatch_error.js b/app/assets/javascripts/ide/oauth_callback_domain_mismatch_error.js
index 6598fe62d4979eca3c159df6d2bf59564b802aa2..bf9678bc498133916978e25a0a3fea58e923fefa 100644
--- a/app/assets/javascripts/ide/oauth_callback_domain_mismatch_error.js
+++ b/app/assets/javascripts/ide/oauth_callback_domain_mismatch_error.js
@@ -1,48 +1,43 @@
 import Vue from 'vue';
 import OAuthDomainMismatchError from './components/oauth_domain_mismatch_error.vue';
+import { parseCallbackUrls, getOAuthCallbackUrl } from './lib/gitlab_web_ide/oauth_callback_urls';
 
 export class OAuthCallbackDomainMismatchErrorApp {
   #el;
-  #callbackUrlOrigins;
+  #callbackUrls;
+  #expectedCallbackUrl;
 
-  constructor(el, callbackUrls) {
+  constructor(el) {
     this.#el = el;
-    this.#callbackUrlOrigins =
-      OAuthCallbackDomainMismatchErrorApp.#getCallbackUrlOrigins(callbackUrls);
+    this.#callbackUrls = parseCallbackUrls(el.dataset.callbackUrls);
+    this.#expectedCallbackUrl = getOAuthCallbackUrl();
   }
 
-  isVisitingFromNonRegisteredOrigin() {
-    return (
-      this.#callbackUrlOrigins.length && !this.#callbackUrlOrigins.includes(window.location.origin)
-    );
+  shouldRenderError() {
+    if (!this.#callbackUrls.length) {
+      return false;
+    }
+
+    return this.#callbackUrls.every(({ url }) => url !== this.#expectedCallbackUrl);
   }
 
   renderError() {
-    const callbackUrlOrigins = this.#callbackUrlOrigins;
+    const callbackUrls = this.#callbackUrls;
+    const expectedCallbackUrl = this.#expectedCallbackUrl;
     const el = this.#el;
 
     if (!el) return null;
 
     return new Vue({
       el,
-      data() {
-        return {
-          callbackUrlOrigins,
-        };
-      },
       render(createElement) {
         return createElement(OAuthDomainMismatchError, {
           props: {
-            callbackUrlOrigins,
+            expectedCallbackUrl,
+            callbackUrls,
           },
         });
       },
     });
   }
-
-  static #getCallbackUrlOrigins(callbackUrls) {
-    if (!callbackUrls) return [];
-
-    return JSON.parse(callbackUrls).map((url) => new URL(url).origin);
-  }
 }
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index b556595bc67e97ddb27002b9b16aaa4e6c1bf1d9..48b7cb86bda46795bba8f2e582556afca006ab41 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -780,3 +780,14 @@ export function buildURLwithRefType({ base = window.location.origin, path, refTy
   }
   return url.pathname + url.search;
 }
+
+export function stripRelativeUrlRootFromPath(path) {
+  const relativeUrlRoot = joinPaths(window.gon.relative_url_root, '/');
+
+  // If we have no relative url root or path doesn't start with it, just return the path
+  if (relativeUrlRoot === '/' || !path.startsWith(relativeUrlRoot)) {
+    return path;
+  }
+
+  return joinPaths('/', path.substring(relativeUrlRoot.length));
+}
diff --git a/config/routes.rb b/config/routes.rb
index 44903738510aa886847f72a94607328a54338037..3fede72eebb3b5a0e53ef5648b89e810f4b51c96 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -145,6 +145,7 @@
       scope :ide, as: :ide, format: false do
         get '/', to: 'ide#index'
         get '/project', to: 'ide#index'
+        # note: This path has a hardcoded reference in the FE `app/assets/javascripts/ide/constants.js`
         get '/oauth_redirect', to: 'ide#oauth_redirect'
 
         scope path: 'project/:project_id', as: :project, constraints: { project_id: Gitlab::PathRegex.full_namespace_route_regex } do
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4620d62ae9c6245e7792227f0b0469ee0bdd14db..560e5e580c160e9f2be4d5f4948058c3cb2b6bdf 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -26939,13 +26939,13 @@ msgstr ""
 msgid "IDE|Contact your administrator or try to open the Web IDE again with another domain."
 msgstr ""
 
-msgid "IDE|Edit"
+msgid "IDE|Could not find a callback URL entry for %{expectedCallbackUrl}."
 msgstr ""
 
-msgid "IDE|Editing this application might affect the functionality of the Web IDE. Ensure the configuration meets the following conditions:"
+msgid "IDE|Edit"
 msgstr ""
 
-msgid "IDE|Error reloading page"
+msgid "IDE|Editing this application might affect the functionality of the Web IDE. Ensure the configuration meets the following conditions:"
 msgstr ""
 
 msgid "IDE|GitLab logo"
diff --git a/package.json b/package.json
index 6a603dfde294cfb5561c24581cc3518fd68c8b6e..42c848bcb177a82e5932a3723c0a8b8e9c7cc475 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,7 @@
     "@gitlab/query-language": "^0.0.5-a-20240806",
     "@gitlab/svgs": "3.112.0",
     "@gitlab/ui": "89.0.0",
-    "@gitlab/web-ide": "^0.0.1-dev-20240731185426",
+    "@gitlab/web-ide": "^0.0.1-dev-20240813211849",
     "@mattiasbuelens/web-streams-adapter": "^0.1.0",
     "@rails/actioncable": "7.0.8-4",
     "@rails/ujs": "7.0.8-4",
diff --git a/patches/@gitlab+web-ide+0.0.1-dev-20240731185426.patch b/patches/@gitlab+web-ide+0.0.1-dev-20240813211849.patch
similarity index 99%
rename from patches/@gitlab+web-ide+0.0.1-dev-20240731185426.patch
rename to patches/@gitlab+web-ide+0.0.1-dev-20240813211849.patch
index 815bfb36eebc2b219942883365f42319c34c83d4..0b40e9ec7d4019edd6add822d43fadec777615e5 100644
--- a/patches/@gitlab+web-ide+0.0.1-dev-20240731185426.patch
+++ b/patches/@gitlab+web-ide+0.0.1-dev-20240813211849.patch
@@ -2950,5 +2950,5 @@ index 6a16dd1..99b1df4 100644
 -	const parentOrigin = searchParams.get('parentOrigin') || window.origin;
 +	const parentOrigin = window.origin;
  	const salt = searchParams.get('salt');
- 
+
  	(async function () {
diff --git a/spec/frontend/ide/components/oauth_domain_mismatch_error_spec.js b/spec/frontend/ide/components/oauth_domain_mismatch_error_spec.js
index 1ce0201f5e9e006059971a0a90041e1edfd0fc2f..2de27b26aaea4cbea7a4bbcc4d1103dde756568b 100644
--- a/spec/frontend/ide/components/oauth_domain_mismatch_error_spec.js
+++ b/spec/frontend/ide/components/oauth_domain_mismatch_error_spec.js
@@ -1,87 +1,111 @@
-import { GlButton, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
+import { GlButton, GlDisclosureDropdown } from '@gitlab/ui';
 import { mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
+import setWindowLocation from 'helpers/set_window_location_helper';
+import { TEST_HOST } from 'helpers/test_constants';
 import OAuthDomainMismatchError from '~/ide/components/oauth_domain_mismatch_error.vue';
 
-const MOCK_CALLBACK_URL_ORIGIN = 'https://example1.com';
-const MOCK_PATH_NAME = '/path/to/ide';
+const MOCK_CALLBACK_URLS = [
+  {
+    base: 'https://example1.com/',
+  },
+  {
+    base: 'https://example2.com/',
+  },
+  {
+    base: 'https://example3.com/relative-path/',
+  },
+];
+const MOCK_CALLBACK_URL = 'https://example.com';
+const MOCK_PATH_NAME = 'path/to/ide';
+
+const EXPECTED_DROPDOWN_ITEMS = MOCK_CALLBACK_URLS.map(({ base }) => ({
+  text: base,
+  href: `${base}${MOCK_PATH_NAME}`,
+}));
 
 describe('OAuthDomainMismatchError', () => {
-  useMockLocationHelper();
-
   let wrapper;
-  let originalLocation;
 
   const findButton = () => wrapper.findComponent(GlButton);
-  const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
-  const findDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
+  const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
 
   const createWrapper = (props = {}) => {
     wrapper = mount(OAuthDomainMismatchError, {
       propsData: {
-        callbackUrlOrigins: [MOCK_CALLBACK_URL_ORIGIN],
+        expectedCallbackUrl: MOCK_CALLBACK_URL,
+        callbackUrls: MOCK_CALLBACK_URLS,
         ...props,
       },
     });
   };
 
   beforeEach(() => {
-    originalLocation = window.location;
-    window.location.pathname = MOCK_PATH_NAME;
-  });
-
-  afterEach(() => {
-    window.location = originalLocation;
+    setWindowLocation(`/${MOCK_PATH_NAME}`);
   });
 
   describe('single callback URL domain passed', () => {
     beforeEach(() => {
-      createWrapper();
+      createWrapper({
+        callbackUrls: MOCK_CALLBACK_URLS.slice(0, 1),
+      });
+    });
+
+    it('renders expected callback URL message', () => {
+      expect(wrapper.text()).toContain(
+        `Could not find a callback URL entry for ${MOCK_CALLBACK_URL}.`,
+      );
     });
 
     it('does not render dropdown', () => {
       expect(findDropdown().exists()).toBe(false);
     });
 
-    it('reloads page with correct url on button click', async () => {
-      findButton().vm.$emit('click');
-      await nextTick();
-
-      expect(window.location.replace).toHaveBeenCalledTimes(1);
-      expect(window.location.replace).toHaveBeenCalledWith(
-        new URL(MOCK_CALLBACK_URL_ORIGIN + MOCK_PATH_NAME).toString(),
-      );
+    it('renders button with correct attributes', () => {
+      const button = findButton();
+      expect(button.exists()).toBe(true);
+      const baseUrl = MOCK_CALLBACK_URLS[0].base;
+      expect(button.text()).toContain(baseUrl);
+      expect(button.attributes('href')).toBe(`${baseUrl}${MOCK_PATH_NAME}`);
     });
   });
 
   describe('multiple callback URL domains passed', () => {
-    const MOCK_CALLBACK_URL_ORIGINS = [MOCK_CALLBACK_URL_ORIGIN, 'https://example2.com'];
-
     beforeEach(() => {
-      createWrapper({ callbackUrlOrigins: MOCK_CALLBACK_URL_ORIGINS });
+      createWrapper();
     });
 
-    it('renders dropdown', () => {
-      expect(findDropdown().exists()).toBe(true);
+    it('renders dropdown with correct items', () => {
+      const dropdown = findDropdown();
+
+      expect(dropdown.exists()).toBe(true);
+      expect(dropdown.props('items')).toStrictEqual(EXPECTED_DROPDOWN_ITEMS);
+    });
+  });
+
+  describe('with erroneous callback from current origin', () => {
+    beforeEach(() => {
+      createWrapper({
+        callbackUrls: MOCK_CALLBACK_URLS.concat({
+          base: `${TEST_HOST}/foo`,
+        }),
+      });
     });
 
-    it('renders dropdown items', () => {
-      const dropdownItems = findDropdownItems();
-      expect(dropdownItems.length).toBe(MOCK_CALLBACK_URL_ORIGINS.length);
-      expect(dropdownItems.at(0).text()).toBe(MOCK_CALLBACK_URL_ORIGINS[0]);
-      expect(dropdownItems.at(1).text()).toBe(MOCK_CALLBACK_URL_ORIGINS[1]);
+    it('filters out item with current origin', () => {
+      expect(findDropdown().props('items')).toStrictEqual(EXPECTED_DROPDOWN_ITEMS);
     });
+  });
 
-    it('reloads page with correct url on dropdown item click', async () => {
-      const dropdownItem = findDropdownItems().at(0);
-      dropdownItem.vm.$emit('select', MOCK_CALLBACK_URL_ORIGIN);
-      await nextTick();
+  describe('when no callback URL passed', () => {
+    beforeEach(() => {
+      createWrapper({
+        callbackUrls: [],
+      });
+    });
 
-      expect(window.location.replace).toHaveBeenCalledTimes(1);
-      expect(window.location.replace).toHaveBeenCalledWith(
-        new URL(MOCK_CALLBACK_URL_ORIGIN + MOCK_PATH_NAME).toString(),
-      );
+    it('does not render dropdown or button', () => {
+      expect(findDropdown().exists()).toBe(false);
+      expect(findButton().exists()).toBe(false);
     });
   });
 });
diff --git a/spec/frontend/ide/helpers.js b/spec/frontend/ide/helpers.js
index e5797ca857530c1f6b67c739ed8f76016e20c676..89d3b14c43299ce94f8ec0c05368218eb57a9aa3 100644
--- a/spec/frontend/ide/helpers.js
+++ b/spec/frontend/ide/helpers.js
@@ -1,7 +1,6 @@
 import * as pathUtils from 'path';
-import { commitActionTypes } from '~/ide/constants';
+import { WEB_IDE_OAUTH_CALLBACK_URL_PATH, commitActionTypes } from '~/ide/constants';
 import { decorateData } from '~/ide/stores/utils';
-import { WEB_IDE_OAUTH_CALLBACK_URL_PATH } from '~/ide/lib/gitlab_web_ide/get_oauth_config';
 
 // eslint-disable-next-line max-params
 export const file = (name = 'name', id = name, type = '', parent = null) =>
diff --git a/spec/frontend/ide/index_spec.js b/spec/frontend/ide/index_spec.js
index 414c963aeb763a60e91d35ada8a98f1ea3364364..36a7f1c4b776ecfccdd99d1fd1724b4d8e4bdd6e 100644
--- a/spec/frontend/ide/index_spec.js
+++ b/spec/frontend/ide/index_spec.js
@@ -2,12 +2,16 @@ import { startIde } from '~/ide/index';
 import { IDE_ELEMENT_ID } from '~/ide/constants';
 import { OAuthCallbackDomainMismatchErrorApp } from '~/ide/oauth_callback_domain_mismatch_error';
 import { initGitlabWebIDE } from '~/ide/init_gitlab_web_ide';
+import { initLegacyWebIDE } from '~/ide/init_legacy_web_ide';
+import setWindowLocation from 'helpers/set_window_location_helper';
+import { TEST_HOST } from 'helpers/test_constants';
 
 jest.mock('~/ide/init_gitlab_web_ide');
+jest.mock('~/ide/init_legacy_web_ide');
 
-const MOCK_CALLBACK_URL = `${window.location.origin}/ide/redirect`;
+const MOCK_MISMATCH_CALLBACK_URL = 'https://example.com/ide/redirect';
 const MOCK_DATA_SET = {
-  callbackUrls: JSON.stringify([MOCK_CALLBACK_URL]),
+  callbackUrls: JSON.stringify([`${TEST_HOST}/-/ide/oauth_redirect`]),
   useNewWebIde: true,
 };
 /**
@@ -27,12 +31,20 @@ const setupMockIdeElement = (customData = MOCK_DATA_SET) => {
 };
 
 describe('startIde', () => {
+  let renderErrorSpy;
+
+  beforeEach(() => {
+    setWindowLocation(`${TEST_HOST}/-/ide/edit/gitlab-org/gitlab`);
+    renderErrorSpy = jest.spyOn(OAuthCallbackDomainMismatchErrorApp.prototype, 'renderError');
+  });
+
   afterEach(() => {
-    document.getElementById(IDE_ELEMENT_ID).remove();
+    document.getElementById(IDE_ELEMENT_ID)?.remove();
   });
 
   describe('when useNewWebIde feature flag is true', () => {
     let ideElement;
+
     beforeEach(async () => {
       ideElement = setupMockIdeElement();
 
@@ -43,43 +55,53 @@ describe('startIde', () => {
       expect(initGitlabWebIDE).toHaveBeenCalledTimes(1);
       expect(initGitlabWebIDE).toHaveBeenCalledWith(ideElement);
     });
-  });
 
-  describe('OAuth callback origin mismatch check', () => {
-    let renderErrorSpy;
-
-    beforeEach(() => {
-      renderErrorSpy = jest.spyOn(OAuthCallbackDomainMismatchErrorApp.prototype, 'renderError');
+    it('does not render error page', () => {
+      expect(renderErrorSpy).not.toHaveBeenCalled();
     });
+  });
+
+  describe('with mismatch callback url', () => {
+    it('renders error page', async () => {
+      setupMockIdeElement({
+        callbackUrls: JSON.stringify([MOCK_MISMATCH_CALLBACK_URL]),
+        useNewWebIde: true,
+      });
 
-    it('does not render error page if no callbackUrl provided', async () => {
-      setupMockIdeElement({ useNewWebIde: true });
       await startIde();
 
-      expect(renderErrorSpy).not.toHaveBeenCalled();
-      expect(initGitlabWebIDE).toHaveBeenCalledTimes(1);
+      expect(renderErrorSpy).toHaveBeenCalledTimes(1);
+      expect(initGitlabWebIDE).not.toHaveBeenCalled();
     });
+  });
+
+  describe('with relative URL location and mismatch callback url', () => {
+    it('renders error page', async () => {
+      setWindowLocation(`${TEST_HOST}/relative-path/-/ide/edit/project`);
 
-    it('does not call renderOAuthDomainMismatchError if no mismatch detected', async () => {
       setupMockIdeElement();
+
       await startIde();
 
-      expect(renderErrorSpy).not.toHaveBeenCalled();
-      expect(initGitlabWebIDE).toHaveBeenCalledTimes(1);
+      expect(renderErrorSpy).toHaveBeenCalledTimes(1);
+      expect(initGitlabWebIDE).not.toHaveBeenCalled();
     });
+  });
 
-    it('renders error page if OAuth callback origin does not match window.location.origin', async () => {
-      const MOCK_MISMATCH_CALLBACK_URL = 'https://example.com/ide/redirect';
-      renderErrorSpy.mockImplementation(() => {});
-      setupMockIdeElement({
-        callbackUrls: JSON.stringify([MOCK_MISMATCH_CALLBACK_URL]),
-        useNewWebIde: true,
-      });
+  describe('when useNewWebIde feature flag is false', () => {
+    beforeEach(async () => {
+      setupMockIdeElement({ useNewWebIde: false });
 
       await startIde();
+    });
 
-      expect(renderErrorSpy).toHaveBeenCalledTimes(1);
-      expect(initGitlabWebIDE).not.toHaveBeenCalled();
+    it('calls initGitlabWebIDE', () => {
+      expect(initLegacyWebIDE).toHaveBeenCalledTimes(1);
+      expect(initGitlabWebIDE).toHaveBeenCalledTimes(0);
+    });
+
+    it('does not render error page', () => {
+      expect(renderErrorSpy).not.toHaveBeenCalled();
     });
   });
 });
diff --git a/spec/frontend/ide/lib/gitlab_web_ide/oauth_callback_urls_spec.js b/spec/frontend/ide/lib/gitlab_web_ide/oauth_callback_urls_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..61a8e3288d8bc9a8db2df8a72599609db90856d2
--- /dev/null
+++ b/spec/frontend/ide/lib/gitlab_web_ide/oauth_callback_urls_spec.js
@@ -0,0 +1,89 @@
+import {
+  parseCallbackUrls,
+  getOAuthCallbackUrl,
+} from '~/ide/lib/gitlab_web_ide/oauth_callback_urls';
+import { logError } from '~/lib/logger';
+import { joinPaths } from '~/lib/utils/url_utility';
+import { IDE_PATH, WEB_IDE_OAUTH_CALLBACK_URL_PATH } from '~/ide/constants';
+import setWindowLocation from 'helpers/set_window_location_helper';
+
+jest.mock('~/lib/logger');
+
+const MOCK_IDE_PATH = joinPaths(IDE_PATH, 'some/path');
+
+describe('ide/lib/oauth_callback_urls', () => {
+  describe('getOAuthCallbackUrl', () => {
+    const mockPath = MOCK_IDE_PATH;
+    const MOCK_RELATIVE_PATH = 'relative-path';
+    const mockPathWithRelative = joinPaths(MOCK_RELATIVE_PATH, MOCK_IDE_PATH);
+
+    const originalHref = window.location.href;
+
+    afterEach(() => {
+      setWindowLocation(originalHref);
+    });
+
+    const expectedBaseUrlWithRelative = joinPaths(window.location.origin, MOCK_RELATIVE_PATH);
+
+    it.each`
+      path                    | expectedCallbackBaseUrl
+      ${mockPath}             | ${window.location.origin}
+      ${mockPathWithRelative} | ${expectedBaseUrlWithRelative}
+    `(
+      'retrieves expected callback URL based on window url',
+      ({ path, expectedCallbackBaseUrl }) => {
+        setWindowLocation(path);
+
+        const actual = getOAuthCallbackUrl();
+        const expected = joinPaths(expectedCallbackBaseUrl, WEB_IDE_OAUTH_CALLBACK_URL_PATH);
+        expect(actual).toEqual(expected);
+      },
+    );
+  });
+  describe('parseCallbackUrls', () => {
+    it('parses the given JSON URL array and returns some metadata for them', () => {
+      const actual = parseCallbackUrls(
+        JSON.stringify([
+          'https://gitlab.com/-/ide/oauth_redirect',
+          'not a url',
+          'https://gdk.test:3443/-/ide/oauth_redirect/',
+          'https://gdk.test:3443/gitlab/-/ide/oauth_redirect#1234?query=foo',
+          'https://example.com/not-a-real-one-/ide/oauth_redirectz',
+        ]),
+      );
+
+      expect(actual).toEqual([
+        {
+          base: 'https://gitlab.com/',
+          url: 'https://gitlab.com/-/ide/oauth_redirect',
+        },
+        {
+          base: 'https://gdk.test:3443/',
+          url: 'https://gdk.test:3443/-/ide/oauth_redirect/',
+        },
+        {
+          base: 'https://gdk.test:3443/gitlab/',
+          url: 'https://gdk.test:3443/gitlab/-/ide/oauth_redirect#1234?query=foo',
+        },
+        {
+          base: 'https://example.com/',
+          url: 'https://example.com/not-a-real-one-/ide/oauth_redirectz',
+        },
+      ]);
+    });
+
+    it('returns empty when given empty', () => {
+      expect(parseCallbackUrls('')).toEqual([]);
+      expect(logError).not.toHaveBeenCalled();
+    });
+
+    it('returns empty when not valid JSON', () => {
+      expect(parseCallbackUrls('babar')).toEqual([]);
+      expect(logError).toHaveBeenCalledWith('Failed to parse callback URLs JSON');
+    });
+
+    it('returns empty when not array JSON', () => {
+      expect(parseCallbackUrls('{}')).toEqual([]);
+    });
+  });
+});
diff --git a/spec/frontend/ide/mount_oauth_callback_spec.js b/spec/frontend/ide/mount_oauth_callback_spec.js
index 9187fffca811497101fb0d3c42d4cd7edbf09f05..97a9a100ec9418debb9a103cbb1490a357b24590 100644
--- a/spec/frontend/ide/mount_oauth_callback_spec.js
+++ b/spec/frontend/ide/mount_oauth_callback_spec.js
@@ -46,7 +46,7 @@ describe('~/ide/mount_oauth_callback', () => {
         clientId: TEST_OAUTH_CLIENT_ID,
         protectRefreshToken: true,
       },
-      gitlabUrl: TEST_HOST,
+      gitlabUrl: `${TEST_HOST}`,
       baseUrl: `${TEST_HOST}/${TEST_GITLAB_WEB_IDE_PUBLIC_PATH}`,
       username: TEST_USERNAME,
     });
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index f9fa2f0e1a53bc820d2233fcb4c5ae5cc2ef5082..0a8c4ee1b2568925144663c5d61ae48434b06f1e 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -1296,4 +1296,21 @@ describe('URL utility', () => {
       expect(urlUtils.buildURLwithRefType({ base, path, refType })).toBe(output);
     });
   });
+
+  describe('stripRelativeUrlRootFromPath', () => {
+    it.each`
+      relativeUrlRoot | path                   | expectation
+      ${''}           | ${'/foo/bar'}          | ${'/foo/bar'}
+      ${'/'}          | ${'/foo/bar'}          | ${'/foo/bar'}
+      ${'/foo'}       | ${'/foo/bar'}          | ${'/bar'}
+      ${'/gitlab/'}   | ${'/gitlab/-/ide/foo'} | ${'/-/ide/foo'}
+    `(
+      'with relative_url_root="$relativeUrlRoot", "$path" should return "$expectation"',
+      ({ relativeUrlRoot, path, expectation }) => {
+        window.gon.relative_url_root = relativeUrlRoot;
+
+        expect(urlUtils.stripRelativeUrlRootFromPath(path)).toBe(expectation);
+      },
+    );
+  });
 });
diff --git a/yarn.lock b/yarn.lock
index 9a38cd923650f43193aff723947fa605ece08799..8b437a42d21ad68c33faadde6866360984444b0e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1378,10 +1378,10 @@
     vue-functional-data-merge "^3.1.0"
     vue-runtime-helpers "^1.1.2"
 
-"@gitlab/web-ide@^0.0.1-dev-20240731185426":
-  version "0.0.1-dev-20240731185426"
-  resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20240731185426.tgz#dd80adac2286131b08ccb13f590befe80a8e1981"
-  integrity sha512-Mfnglz0j1UTNtSP+PMIlasbUQMSrUZFjrhUgy9KwdzOIUqe3SdjXs9hAaeYWQ2KAM8libarsqrHTVvstsLJKhw==
+"@gitlab/web-ide@^0.0.1-dev-20240813211849":
+  version "0.0.1-dev-20240813211849"
+  resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20240813211849.tgz#da0e6c8ce1137ac7983dd743e0372d6fc8554155"
+  integrity sha512-JrTgCh+PiWe4pQzdy7KuvIaZjPazoQdBOVzlPzYr7e9tP95w9l5Wy+4I2et4vva0NKRmYYllaqGxxbRTyCCfcA==
 
 "@graphql-eslint/eslint-plugin@3.20.1":
   version "3.20.1"
@@ -12914,16 +12914,7 @@ string-length@^4.0.1:
     char-regex "^1.0.2"
     strip-ansi "^6.0.0"
 
-"string-width-cjs@npm:string-width@^4.2.0":
-  version "4.2.3"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
-  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
-  dependencies:
-    emoji-regex "^8.0.0"
-    is-fullwidth-code-point "^3.0.0"
-    strip-ansi "^6.0.1"
-
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -12975,7 +12966,7 @@ string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -12989,13 +12980,6 @@ strip-ansi@^5.2.0:
   dependencies:
     ansi-regex "^4.1.0"
 
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
-  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
-  dependencies:
-    ansi-regex "^5.0.1"
-
 strip-ansi@^7.0.1, strip-ansi@^7.1.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -14670,7 +14654,7 @@ worker-loader@^3.0.8:
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -14688,15 +14672,6 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
-wrap-ansi@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
-  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
 wrap-ansi@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"