openedx / openedx/openedx-platform
production.py unconditionally overwrites an explicitly configured SEARCH_ENGINE, making non-Elasticsearch backends unreachable
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 8.2k
- Forks
- 4.4k
- Avg merge
- 6d 18h
- Merged PRs (30d)
- 42
Description
Summary
lms/envs/production.py and cms/envs/production.py overwrite SEARCH_ENGINE with
search.elastic.ElasticSearchEngine whenever certain feature flags are on — even when the
operator has explicitly set SEARCH_ENGINE to something else in LMS_CFG / CMS_CFG. This
makes the documented Typesense backend (and Meilisearch, and any custom engine) unreachable on
any deployment that has courseware search enabled.
Affected versions
Present and identical on all three branches checked on 2026-08-14:
master@0c243f5a7release/verawood@259473c5drelease/ulmo
Details
CMS — cms/envs/production.py (master :283-285, verawood :286-288):
if ENABLE_COURSEWARE_INDEX or ENABLE_LIBRARY_INDEX:
# Use ElasticSearch for the search engine
SEARCH_ENGINE = "search.elastic.ElasticSearchEngine"
LMS — lms/envs/production.py (master :322-330, verawood :338-346):
if (
ENABLE_COURSEWARE_SEARCH or
ENABLE_DASHBOARD_SEARCH or
ENABLE_COURSE_DISCOVERY or
ENABLE_TEAMS
):
# Use ElasticSearch as the search engine herein
SEARCH_ENGINE = "search.elastic.ElasticSearchEngine"
Both run after the YAML config has been applied to the module namespace
(vars().update(...) over _YAML_TOKENS, cms/envs/production.py:70), so an explicitly
configured SEARCH_ENGINE is silently discarded.
The FeaturesProxy interaction makes this hard to work around
Since FEATURES = FeaturesProxy(globals()) (cms/envs/production.py:35,
lms/envs/production.py:39), and FeaturesProxy.__setitem__ writes straight into the
settings module's namespace:
def __setitem__(self, key, value):
warnings.warn(...)
self.ns[key] = value
...the YAML FEATURES ingest loop (cms/envs/production.py:154-155) writes those keys as
module globals. So setting ENABLE_COURSEWARE_INDEX under FEATURES: in CMS_CFG is
indistinguishable from setting the top-level name, and there is no way to enable courseware
indexing without also triggering the SEARCH_ENGINE override.
Minimal reproduction using the real proxy class:
from openedx.core.lib.features_setting_proxy import FeaturesProxy
ns = {'ENABLE_COURSEWARE_INDEX': False, 'ENABLE_LIBRARY_INDEX': False,
'SEARCH_ENGINE': 'search.typesense.TypesenseEngine'}
FEATURES = FeaturesProxy(ns)
for feature, value in {'ENABLE_COURSEWARE_INDEX': True}.items(): # production.py:154-155
FEATURES[feature] = value
if ns['ENABLE_COURSEWARE_INDEX'] or ns['ENABLE_LIBRARY_INDEX']: # production.py:283
ns['SEARCH_ENGINE'] = 'search.elastic.ElasticSearchEngine'
print(ns['SEARCH_ENGINE']) # search.elastic.ElasticSearchEngine
Impact
An operator following
Use Typesense search backend
and setting SEARCH_ENGINE: search.typesense.TypesenseEngine in LMS_CFG/CMS_CFG will find
that:
- the LMS reads from Elasticsearch as soon as any of
ENABLE_COURSEWARE_SEARCH,
ENABLE_DASHBOARD_SEARCH,ENABLE_COURSE_DISCOVERYorENABLE_TEAMSis on — i.e. in
virtually every real deployment; - the CMS writes to Elasticsearch as soon as courseware indexing is enabled.
There is no warning or log line; the configured value is simply gone.
Suggested fix
Only apply the Elasticsearch default when SEARCH_ENGINE was not explicitly configured, e.g.
if (ENABLE_COURSEWARE_INDEX or ENABLE_LIBRARY_INDEX) and 'SEARCH_ENGINE' not in _YAML_TOKENS:
SEARCH_ENGINE = "search.elastic.ElasticSearchEngine"
or drop the override entirely and let SEARCH_ENGINE default in common.py, which would be
consistent with how other backends are selected.
I'm happy to open a PR for whichever shape maintainers prefer.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the SEARCH_ENGINE override blocks in lms/envs/production.py and cms/envs/production.py, then read the YAML token handling and the default in common.py. Verify how FeaturesProxy and _YAML_TOKENS affect explicit configuration. Done means an explicitly configured engine remains selected while the Elasticsearch default still applies when no engine was configured.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, search
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100