harry0703/MoneyPrinterTurbo · error · ValueError
selectbox options cannot be empty
Error message
selectbox options cannot be empty: {key} What it means
stable_selectbox() is a WebUI helper that wraps Streamlit selectbox with stable business values as options (to survive reruns and language switches). It raises ValueError when the options list is empty, because a selectbox with zero options cannot render or keep state. The empty list almost always means an upstream data source (e.g. available voices, songs, providers) produced nothing at render time.
Solutions
- Check why the upstream list is empty: e.g. for song/BGM pickers ensure resource/songs or storage/bgm actually contains readable files
- Supply a sensible non-empty fallback or default option list at the call site instead of passing a possibly-empty generator result
- If empty is legitimately possible, guard the call site: skip rendering the selectbox (and show an informative message) when options is empty rather than calling the helper
Example fix
# before
stable_selectbox(tr("Background Music"), songs, songs[0] if songs else None, key="bgm_song")
# after
if not songs:
st.info(tr("No background music available. Upload a song first."))
else:
stable_selectbox(tr("Background Music"), songs, songs[0], key="bgm_song") Defensive patterns
Strategy: type-guard
Validate before calling
options = list(options)
if not options:
st.info("No options available for this control yet.")
return # skip rendering instead of raising Type guard
def has_select_options(options) -> bool:
"""Narrow before calling stable_selectbox."""
return bool(list(options or [])) Try / catch
try:
value = stable_selectbox(label, options, default_value, key=key)
except ValueError as exc:
if "selectbox options cannot be empty" in str(exc):
st.warning(f"{label}: no options available")
value = None
else:
raise Prevention
- Materialize generator/list options with list(...) and truth-check before every stable_selectbox call
- Keep seed data present (e.g. at least one built-in song) so option lists are never empty on fresh installs
- Log a warning with the widget key whenever an options source comes back empty, to catch config drift early
When it happens
Trigger: Any call stable_selectbox(label, options=[], ...) — e.g. the list of BGM songs from storage/bgm + resource/songs is empty, a provider list filters to zero entries, or a config directory yields no items — while the page is being rendered.
Common situations: Fresh install with resource/songs and storage/bgm empty so the BGM picker has no candidates; a config file or directory the options derive from is missing/renamed; a filter (language, provider availability) removes every option; environment where a listing API returns nothing.
Related errors
AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14).
Data as JSON: /api/errors/65264ecd2c9ae0ef.
Report an issue: GitHub.
Appendix: source
Thrown at webui/Main.py:1630
def localized_widget_key(name, *parts):
# 部分 Streamlit selectbox 使用稳定 key 记住选择状态,但展示文本来自 locale。
# 语言切换时把语言也放进 key,可以强制重建控件,避免选中项仍显示旧语言。
language = st.session_state.get("ui_language", config.ui.get("language", ""))
suffix_parts = [name, language, *[str(part) for part in parts if part]]
return "_".join(suffix_parts)
def stable_selectbox(label, options, default_value, key, format_func=None, **kwargs):
# Streamlit 1.59 对 selectbox 的状态复用更敏感:如果控件没有固定 key,
# 或者真实选项只是一组临时下标,页面 rerun 后容易被重新计算的 index 覆盖,
# 表现为用户第一次选择不生效、需要再选一次。这个 helper 统一用稳定业务值
# 作为真实选项,并在 session_state 里保存该值;展示文案只通过 format_func
# 转换,避免翻译文案、选项顺序或上游配置变化影响选择状态。
options = list(options)
if not options:
raise ValueError(f"selectbox options cannot be empty: {key}")
if default_value not in options:
default_value = options[0]
widget_key = localized_widget_key(key)
selected_value = st.session_state.get(widget_key)
accepts_custom_value = bool(kwargs.get("accept_new_options"))
has_valid_custom_value = (
accepts_custom_value
and isinstance(selected_value, str)
and bool(selected_value.strip())
)
if selected_value not in options and not has_valid_custom_value:
# 如果上游选项发生变化(例如切换 TTS provider 后声音列表变了),
# 旧值已经不合法。控件创建前直接初始化 session_state,之后只让 key
# 管理状态,不再同时传入 index。这样可以避免 Streamlit 在 rerun 时
# 用重新计算的 index 覆盖用户刚选择的值,导致第一次选择不生效。
st.session_state[widget_key] = default_valueView on GitHub (pinned to 1f9f19c202)