Course catalog is empty

Hi everyone
Hoping someone can give me some pointers here, I’ve noticed that it’s impossible for any of my users to browse any courses (entirely 100% of all courses)
They can see their enrolled courses on the /learner-dashboard page but the /catalog/courses page (Discover New) says

No courses available
There are currently no courses available in the catalog. Please check back later for new offerings.

If I look under https://discovery.lms.domain.tld/admin/course_metadata/course/ then i can see all the courses there.
If I try reindex courses it proceeds to index them fine using tutor local run cms ./manage.py cms reindex_course --all
If I try refresh course metadata using tutor local run discovery ./manage.py refresh_course_metadata --partner_code=openedx then it gives timeout errors like these:

2026-02-04 11:49:59,614 INFO 1 [course_discovery.apps.course_metadata.data_loaders.api] /openedx/discovery/course_discovery/apps/course_metadata/data_loaders/api.py:63 - Refreshing Courses and CourseRuns from https://lms.domain.tld/api/courses/v1/...
2026-02-04 11:49:59,614 INFO 1 [course_discovery.apps.course_metadata.data_loaders.api] /openedx/discovery/course_discovery/apps/course_metadata/data_loaders/api.py:109 - Requesting course run page 1...
2026-02-04 11:52:09,839 INFO 1 [backoff] /openedx/venv/lib/python3.12/site-packages/backoff/_common.py:105 - Backing off _make_request(...) for 17.9s (requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host='lms.domain.tld', port=443): Max retries exceeded with url: /api/courses/v1/courses/?page=1&page_size=50&username=discovery&active_only=True (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x7fcb9b913740>, 'Connection to lms.domain.tld timed out. (connect timeout=None)')))
2026-02-04 11:52:27,711 INFO 1 [course_discovery.apps.course_metadata.data_loaders.api] /openedx/discovery/course_discovery/apps/course_metadata/data_loaders/api.py:109 - Requesting course run page 1...
2026-02-04 11:54:37,294 INFO 1 [backoff] /openedx/venv/lib/python3.12/site-packages/backoff/_common.py:105 - Backing off _make_request(...) for 8.8s (requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host='lms.domain.tld', port=443): Max retries exceeded with url: /api/courses/v1/courses/?page=1&page_size=50&username=discovery&active_only=True (Caused by ConnectTimeoutError(<urllib3.connection.HTTPSConnection object at 0x7fcb9b930140>, 'Connection to lms.domain.tld timed out. (connect timeout=None)')))
2026-02-04 11:54:46,141 INFO 1 [course_discovery.apps.course_metadata.data_loaders.api] /openedx/discovery/course_discovery/apps/course_metadata/data_loaders/api.py:109 - Requesting course run page 1...

it seems to look a lot like this here: max-retries-exceeded-with-url but I don’t understand what to make of the info here, doesn’t exactly say what the fix is.

where am i going wrong?
Thank you

tutor, version 21.0.0
discovery, version 21.0.1
mfe, version 21.0.0

1 Like

Hey @joel.edwards could I clarify, are you using the new Catalog MFE?

Hi @sarina
Yes that’s right, I do have catalog MFE installed, did it using this plugin with the updated edx-search version as was described in another recent post like this:

from tutormfe.hooks import MFE_APPS
from tutor import hooks


INSTALL_SEARCH_440 = r"""
RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared \
    pip install "edx-search==4.4.0"
"""

hooks.Filters.ENV_PATCHES.add_items([
    ("openedx-dockerfile-post-python-requirements", INSTALL_SEARCH_440),
    ("openedx-dev-dockerfile-post-python-requirements", INSTALL_SEARCH_440),
])

@MFE_APPS.add()
def _add_catalog_mfe(mfes):
    mfes["catalog"] = {
        "repository": "https://github.com/openedx/frontend-app-catalog.git",
        "port": 1998,
        "version": "master", # optional, will default to the Open edX current tag.
    }
    return mfes

catalog_mfe_url = """
CATALOG_MICROFRONTEND_URL = "http://{{ MFE_HOST }}/catalog"
"""

catalog_mfe_url_dev = """
CATALOG_MICROFRONTEND_URL = "http://{{ MFE_HOST }}:{{ get_mfe('catalog').port }}/catalog"
"""

env_items = [
    (
        "openedx-common-settings",
        catalog_mfe_url,
    ),
    (
        "openedx-development-settings",
        catalog_mfe_url_dev,
    ),
    (
        "openedx-lms-common-settings",
        "ENABLE_CATALOG_MICROFRONTEND = True",
    ),
]

for item in env_items:
    hooks.Filters.ENV_PATCHES.add_item(item)

Have you run with tutor-discovery enabled in the past? If so, did the refresh_course_metadata task stop working just on the most recent upgrade to Ulmo?

this seems promising. Do you have shell access to the Discovery app? If so, could you start a shell into it and run ping lms.domain.tld and nsloookup lms.domain.tld, and share some of the output ?

I have in prior versions enabled the discovery plugin though I’m not convinced I’d ever configured it properly before, I think I may have just somewhat given up and left it. As far as I remember this is the first time I’ve ever tried running the refresh_course_metadata task.

I couldn’t figure out how to get that to work actually, seems like ping/nslookup are not included in the containers by default and couldn’t figure out how to add them.

For what it’s worth, I’ve now disabled catalog and discovery followed by rebuilding everything and it looks like my courses are showing again…

Ah, right, those commands would only be available on the dev containers, not production ones. My mistake

Glad your courses are showing again, sorry we couldn’t be more helpful. For what it’s worth, unless you specifically need any features from tutor-discovery, I would recommend leaving it disabled. In my experieince, it’s more trouble than it’s worth. But if you do need tutor-discovery, let me know and I can lend a hand debugging that connection issue.

@kmccormick
I think I might have some more info now. in short I was simply trying to test out the Catalog MFE so I’ve removed others like Discovery and that got the course listing to work again.
but now if I want to use catalog mfe I need to install edx-search 4.4.0 like:

from tutor import hooks

INSTALL_SEARCH_440 = r"""
RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared \
    pip install "edx-search==4.4.0"
"""

hooks.Filters.ENV_PATCHES.add_items([
    ("openedx-dockerfile-post-python-requirements", INSTALL_SEARCH_440),
    ("openedx-dev-dockerfile-post-python-requirements", INSTALL_SEARCH_440),
])

but this gives an error building the openedx image:

tutor images build openedx
Building image docker.io/overhangio/openedx:21.0.0-indigo
docker buildx build --tag=docker.io/overhangio/openedx:21.0.0-indigo --output=type=docker --cache-from=type=registry,ref=docker.io/overhangio/openedx:21.0.0-indigo-cache /home/tutor/.local/share/tutor/env/build/openedx
[+] Building 6.5s (27/66)                                                                                                                                                               docker-container:max3cpu
 => [internal] load build definition from Dockerfile                                                                                                                                                        0.0s
 => => transferring dockerfile: 12.68kB                                                                                                                                                                     0.0s
 => resolve image config for docker-image://docker.io/docker/dockerfile:1                                                                                                                                   0.3s
 => CACHED docker-image://docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6                                                                             0.0s
 => => resolve docker.io/docker/dockerfile:1@sha256:b6afd42430b15f2d2a4c5a02b919e98a525b785b1aaff16747d2f623364e39b6                                                                                        0.0s
 => [internal] load metadata for docker.io/library/ubuntu:22.04                                                                                                                                             1.2s
 => [internal] load metadata for ghcr.io/astral-sh/uv:0.9.18                                                                                                                                                2.1s
 => [internal] load metadata for docker.io/powerman/dockerize:0.19.0                                                                                                                                        2.4s
 => [internal] load .dockerignore                                                                                                                                                                           0.0s
 => => transferring context: 2B                                                                                                                                                                             0.0s
 => importing cache manifest from docker.io/overhangio/openedx:21.0.0-indigo-cache                                                                                                                          1.4s
 => => inferred cache manifest type: application/vnd.oci.image.manifest.v1+json                                                                                                                             0.0s
 => [internal] load build context                                                                                                                                                                           0.0s
 => => transferring context: 8.92kB                                                                                                                                                                         0.0s
 => FROM ghcr.io/astral-sh/uv:0.9.18@sha256:5713fa8217f92b80223bc83aac7db36ec80a84437dbc0d04bbc659cae030d8c9                                                                                                0.0s
 => => resolve ghcr.io/astral-sh/uv:0.9.18@sha256:5713fa8217f92b80223bc83aac7db36ec80a84437dbc0d04bbc659cae030d8c9                                                                                          0.0s
 => [minimal 1/3] FROM docker.io/library/ubuntu:22.04@sha256:c7eb020043d8fc2ae0793fb35a37bff1cf33f156d4d4b12ccc7f3ef8706c38b1                                                                               0.0s
 => => resolve docker.io/library/ubuntu:22.04@sha256:c7eb020043d8fc2ae0793fb35a37bff1cf33f156d4d4b12ccc7f3ef8706c38b1                                                                                       0.0s
 => [code 3/4] ADD --keep-git-dir=true https://github.com/openedx/edx-platform.git#release/ulmo.1 .                                                                                                         1.1s
 => FROM docker.io/powerman/dockerize:0.19.0@sha256:a34158d699e065609fc845e7cdc1e070f185afdbca317fe4931aaea04818f8bf                                                                                        0.0s
 => => resolve docker.io/powerman/dockerize:0.19.0@sha256:a34158d699e065609fc845e7cdc1e070f185afdbca317fe4931aaea04818f8bf                                                                                  0.0s
 => CACHED [minimal 2/3] RUN --mount=type=cache,target=/var/cache/apt,sharing=locked     --mount=type=cache,target=/var/lib/apt,sharing=locked     apt update &&     apt install -y build-essential curl g  0.0s
 => CACHED [minimal 3/3] COPY --from=ghcr.io/astral-sh/uv:0.9.18 /uv /usr/local/bin/uv                                                                                                                      0.0s
 => CACHED [code 1/4] RUN mkdir -p /openedx/edx-platform                                                                                                                                                    0.0s
 => CACHED [code 2/4] WORKDIR /openedx/edx-platform                                                                                                                                                         0.0s
 => CACHED [code 3/4] ADD --keep-git-dir=true https://github.com/openedx/edx-platform.git#release/ulmo.1 .                                                                                                  0.0s
 => CACHED [code 4/4] RUN git config --global user.email "tutor@overhang.io"   && git config --global user.name "Tutor"                                                                                     0.0s
 => CACHED [edx-platform 1/1] COPY --from=code /openedx/edx-platform /                                                                                                                                      0.0s
 => CACHED [python 1/4] RUN --mount=type=cache,target=/var/cache/apt,sharing=locked     --mount=type=cache,target=/var/lib/apt,sharing=locked     apt update &&     apt install -y libssl-dev zlib1g-dev l  0.0s
 => CACHED [python 2/4] RUN git clone https://github.com/pyenv/pyenv /opt/pyenv --branch v2.3.36 --depth 1                                                                                                  0.0s
 => CACHED [python 3/4] RUN /opt/pyenv/bin/pyenv install 3.11.8                                                                                                                                             0.0s
 => CACHED [python 4/4] RUN /opt/pyenv/versions/3.11.8/bin/python -m venv /openedx/venv                                                                                                                     0.0s
 => CACHED [python-requirements 1/7] RUN --mount=type=cache,target=/var/cache/apt,sharing=locked     --mount=type=cache,target=/var/lib/apt,sharing=locked     apt update     && apt install -y software-p  0.0s
 => CACHED [python-requirements 2/7] RUN --mount=type=cache,target=/openedx/.cache/pip,sharing=shared     uv pip install     setuptools==69.1.1 pip==24.0 wheel==0.43.0                                     0.0s
 => ERROR [python-requirements 3/7] RUN --mount=type=bind,from=edx-platform,source=/requirements/edx/base.txt,target=/openedx/edx-platform/requirements/edx/base.txt     --mount=type=bind,from=edx-platfo  1.3s
------
 > [python-requirements 3/7] RUN --mount=type=bind,from=edx-platform,source=/requirements/edx/base.txt,target=/openedx/edx-platform/requirements/edx/base.txt     --mount=type=bind,from=edx-platform,source=/requirements/edx/assets.txt,target=/openedx/edx-platform/requirements/edx/assets.txt     --mount=type=cache,target=/openedx/.cache/pip,sharing=shared     uv pip install -r /openedx/edx-platform/requirements/edx/base.txt -r /openedx/edx-platform/requirements/edx/assets.txt:
0.140 Using Python 3.11.8 environment at: /openedx/venv
1.257   × Failed to build `loremipsum==1.0.5`
1.257   ├─▶ The build backend returned an error
1.257   ╰─▶ Call to `setuptools.build_meta:__legacy__.build_wheel` failed (exit
1.257       status: 1)
1.257
1.257       [stderr]
1.257       Traceback (most recent call last):
1.257         File "<string>", line 14, in <module>
1.257         File
1.257       "/openedx/.cache/pip/builds-v0/.tmpgTeYXs/lib/python3.11/site-packages/setuptools/build_meta.py",
1.257       line 333, in get_requires_for_build_wheel
1.257           return self._get_build_requires(config_settings, requirements=[])
1.257                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1.257         File
1.257       "/openedx/.cache/pip/builds-v0/.tmpgTeYXs/lib/python3.11/site-packages/setuptools/build_meta.py",
1.257       line 301, in _get_build_requires
1.257           self.run_setup()
1.257         File
1.257       "/openedx/.cache/pip/builds-v0/.tmpgTeYXs/lib/python3.11/site-packages/setuptools/build_meta.py",
1.257       line 520, in run_setup
1.257           super().run_setup(setup_script=setup_script)
1.257         File
1.257       "/openedx/.cache/pip/builds-v0/.tmpgTeYXs/lib/python3.11/site-packages/setuptools/build_meta.py",
1.257       line 317, in run_setup
1.257           exec(code, locals())
1.257         File "<string>", line 13, in <module>
1.257         File
1.257       "/openedx/.cache/pip/sdists-v9/pypi/loremipsum/1.0.5/LKbpG553hq161wdZGTyob/src/loremipsum/__init__.py",
1.257       line 61, in <module>
1.257           from .generator import Generator, DictionaryError, SampleError
1.257         File
1.257       "/openedx/.cache/pip/sdists-v9/pypi/loremipsum/1.0.5/LKbpG553hq161wdZGTyob/src/loremipsum/generator.py",
1.257       line 8, in <module>
1.257           from pkg_resources import resource_string
1.257       ModuleNotFoundError: No module named 'pkg_resources'
1.257
1.257       hint: This error likely indicates that `loremipsum@1.0.5` depends
1.257       on `pkg_resources`, but doesn't declare it as a build dependency. If
1.257       `loremipsum` is a first-party package, consider adding `pkg_resources`
1.257       to its `build-system.requires`. Otherwise, either add it to your
1.257       `pyproject.toml` under:
1.257
1.257       [tool.uv.extra-build-dependencies]
1.257       loremipsum = ["pkg_resources"]
1.257
1.257       or `uv pip install pkg_resources` into the environment and re-run with
1.257       `--no-build-isolation`.
------
Dockerfile:91
--------------------
  90 |     # Install base requirements and asset-building requirements
  91 | >>> RUN --mount=type=bind,from=edx-platform,source=/requirements/edx/base.txt,target=/openedx/edx-platform/requirements/edx/base.txt \
  92 | >>>     --mount=type=bind,from=edx-platform,source=/requirements/edx/assets.txt,target=/openedx/edx-platform/requirements/edx/assets.txt \
  93 | >>>     --mount=type=cache,target=/openedx/.cache/pip,sharing=shared \
  94 | >>>     $PIP_COMMAND install -r /openedx/edx-platform/requirements/edx/base.txt -r /openedx/edx-platform/requirements/edx/assets.txt
  95 |
--------------------
ERROR: failed to build: failed to solve: process "/bin/sh -c $PIP_COMMAND install -r /openedx/edx-platform/requirements/edx/base.txt -r /openedx/edx-platform/requirements/edx/assets.txt" did not complete successfully: exit code: 1
Error: Command failed with status 1: docker buildx build --tag=docker.io/overhangio/openedx:21.0.0-indigo --output=type=docker --cache-from=type=registry,ref=docker.io/overhangio/openedx:21.0.0-indigo-cache /home/tutor/.local/share/tutor/env/build/openedx

But without edx-search==4.4.0 then I’m missing the endpoint unstable/v0/course_list_search thus I’m getting a 404 when listing courses, that’s why everything is empty:

lms-1 | [pid: 14|app: 0|req: 23/46] 172.18.0.2 () {68 vars in 4107 bytes} [Mon Feb 9 09:15:38 2026] POST /search/unstable/v0/course_list_search/ => generated 9770 bytes in 69 msecs (HTTP/1.1 404) 10 headers in 597 bytes (1 switches on core 0)
caddy-1 | {"level":"error","ts":1770628538.3851182,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_ip":"x.x.x.x","remote_port":"53475","client_ip":"x.x.x.x","proto":"HTTP/3.0","method":"POST","host":"lms.domain.tld","uri":"/search/unstable/v0/course_list_search/","tls":{"resumed":true,"version":772,"cipher_suite":4865,"proto":"h3","server_name":"lms.domain.tld"}},"bytes_read":367,"user_id":"","duration":0.069913141,"size":3356,"status":404}

I think you’ve hit a completely unrelated issue which is actively breaking the openedx and Tutor build for everyone, regardless of whether they install that Catalog MFE support plugin. The issue seems to stem from a breaking change in setuptools v82 which was released yesterday. We’re debugging it now in the OeX Slack, in the #dev channel. I’ll update here once we figure it out.

1 Like

Hey @joel.edwards , the latest version of Tutor fixes that pkg_resources build error and also installs edx-search==4.4.0 so that you no longer need a plugin to do it: Release v21.0.1 · overhangio/tutor · GitHub

Let me know how it works for you!

Sorry I actually forgot to update here but it did in fact work, thanks Kyle :slight_smile:

1 Like