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 @ 0c243f5a7
release/verawood @ 259473c5d
release/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_DISCOVERY or ENABLE_TEAMS is 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.
Summary
lms/envs/production.pyandcms/envs/production.pyoverwriteSEARCH_ENGINEwithsearch.elastic.ElasticSearchEnginewhenever certain feature flags are on — even when theoperator has explicitly set
SEARCH_ENGINEto something else inLMS_CFG/CMS_CFG. Thismakes 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/ulmoDetails
CMS —
cms/envs/production.py(master:283-285, verawood:286-288):LMS —
lms/envs/production.py(master:322-330, verawood:338-346):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 explicitlyconfigured
SEARCH_ENGINEis silently discarded.The
FeaturesProxyinteraction makes this hard to work aroundSince
FEATURES = FeaturesProxy(globals())(cms/envs/production.py:35,lms/envs/production.py:39), andFeaturesProxy.__setitem__writes straight into thesettings module's namespace:
...the YAML
FEATURESingest loop (cms/envs/production.py:154-155) writes those keys asmodule globals. So setting
ENABLE_COURSEWARE_INDEXunderFEATURES:inCMS_CFGisindistinguishable from setting the top-level name, and there is no way to enable courseware
indexing without also triggering the
SEARCH_ENGINEoverride.Minimal reproduction using the real proxy class:
Impact
An operator following
Use Typesense search backend
and setting
SEARCH_ENGINE: search.typesense.TypesenseEngineinLMS_CFG/CMS_CFGwill findthat:
ENABLE_COURSEWARE_SEARCH,ENABLE_DASHBOARD_SEARCH,ENABLE_COURSE_DISCOVERYorENABLE_TEAMSis on — i.e. invirtually every real deployment;
There is no warning or log line; the configured value is simply gone.
Suggested fix
Only apply the Elasticsearch default when
SEARCH_ENGINEwas not explicitly configured, e.g.or drop the override entirely and let
SEARCH_ENGINEdefault incommon.py, which would beconsistent with how other backends are selected.
I'm happy to open a PR for whichever shape maintainers prefer.