Problem
The Pydantic v1 → v2 migration in v1.13.0 changed the app's env var convention
from single-underscore prefixes (BOT_, MYSQL_, CHANNEL_, ROLE_) to
double-underscore nested delimiters (BOT__, DATABASE__, CHANNEL__,
ROLE__). A few spots were missed or are now confusingly inconsistent with
that change.
Issues
1. scripts/seed_dynamic_roles.py still uses the old single-underscore convention
It reads env vars via dotenv_values() directly and hardcodes
env_var = f"ROLE_{suffix}", independent of RolesSettings (ROLE__*) and
RoleManager._ENV_FALLBACK_MAP. Proposed fix: update the seed script to read
ROLE__{suffix}, or document why it's intentionally separate.
2. BOT_ENVIRONMENT vs bot.ENVIRONMENT/BOT__ENVIRONMENT — near-identical names, different purposes
src/core/config.py reads os.environ.get("BOT_ENVIRONMENT") directly to pick
the env file, while BotSettings.ENVIRONMENT (Sentry tagging,
src/__init__.py:71) comes from BOT__ENVIRONMENT. Both must be set for prod
to behave correctly, and the one-character difference is an easy foot-gun.
Proposed fix: rename the raw check to something distinct, e.g. APP_ENV_FILE.
3. SEASON_ID was silently broken before this release and may still be unset in prod
.test.env used CURRENT_SEASON_ID pre-v1.13.0, which never matched the
always-unprefixed Global.SEASON_ID field — silently ignored. Fixed in
.test.env in v1.13.0, but production Vault may still only define
CURRENT_SEASON_ID. Proposed fix: audit production Vault, add SEASON_ID
with the real value.
4. DatabaseSettings.ASYNC is declared but not wired, and DB connection info is duplicated across two conventions
assemble_db_connection() always hardcodes mariadb+asyncmy:// regardless of
ASYNC. Meanwhile alembic/env.py::get_url() bypasses DatabaseSettings
entirely, reading raw MYSQL_HOST/MYSQL_PORT/MYSQL_USER/MYSQL_PASSWORD/MYSQL_DATABASE
(pre-migration convention) and hardcoding mariadb+pymysql:// since Alembic
needs a sync driver.
Proposed fix: make assemble_db_connection() pick the driver based on ASYNC
(asyncmy when true/default, pymysql when false), and have
alembic/env.py::get_url() call settings.database.assemble_db_connection(async_=False)
instead of re-implementing credential reading against a stale env convention.
startup.sh's mysqladmin ping readiness check also reads raw MYSQL_* —
that's shell-level and can stay as-is, but note it as related context.
Out of scope
RoleManager._ENV_FALLBACK_MAP is explicitly documented as transition-only
("will be removed in follow-up PR") — tracked separately.
Problem
The Pydantic v1 → v2 migration in v1.13.0 changed the app's env var convention
from single-underscore prefixes (
BOT_,MYSQL_,CHANNEL_,ROLE_) todouble-underscore nested delimiters (
BOT__,DATABASE__,CHANNEL__,ROLE__). A few spots were missed or are now confusingly inconsistent withthat change.
Issues
1.
scripts/seed_dynamic_roles.pystill uses the old single-underscore conventionIt reads env vars via
dotenv_values()directly and hardcodesenv_var = f"ROLE_{suffix}", independent ofRolesSettings(ROLE__*) andRoleManager._ENV_FALLBACK_MAP. Proposed fix: update the seed script to readROLE__{suffix}, or document why it's intentionally separate.2.
BOT_ENVIRONMENTvsbot.ENVIRONMENT/BOT__ENVIRONMENT— near-identical names, different purposessrc/core/config.pyreadsos.environ.get("BOT_ENVIRONMENT")directly to pickthe env file, while
BotSettings.ENVIRONMENT(Sentry tagging,src/__init__.py:71) comes fromBOT__ENVIRONMENT. Both must be set for prodto behave correctly, and the one-character difference is an easy foot-gun.
Proposed fix: rename the raw check to something distinct, e.g.
APP_ENV_FILE.3.
SEASON_IDwas silently broken before this release and may still be unset in prod.test.envusedCURRENT_SEASON_IDpre-v1.13.0, which never matched thealways-unprefixed
Global.SEASON_IDfield — silently ignored. Fixed in.test.envin v1.13.0, but production Vault may still only defineCURRENT_SEASON_ID. Proposed fix: audit production Vault, addSEASON_IDwith the real value.
4.
DatabaseSettings.ASYNCis declared but not wired, and DB connection info is duplicated across two conventionsassemble_db_connection()always hardcodesmariadb+asyncmy://regardless ofASYNC. Meanwhilealembic/env.py::get_url()bypassesDatabaseSettingsentirely, reading raw
MYSQL_HOST/MYSQL_PORT/MYSQL_USER/MYSQL_PASSWORD/MYSQL_DATABASE(pre-migration convention) and hardcoding
mariadb+pymysql://since Alembicneeds a sync driver.
Proposed fix: make
assemble_db_connection()pick the driver based onASYNC(
asyncmywhen true/default,pymysqlwhen false), and havealembic/env.py::get_url()callsettings.database.assemble_db_connection(async_=False)instead of re-implementing credential reading against a stale env convention.
startup.sh'smysqladmin pingreadiness check also reads rawMYSQL_*—that's shell-level and can stay as-is, but note it as related context.
Out of scope
RoleManager._ENV_FALLBACK_MAPis explicitly documented as transition-only("will be removed in follow-up PR") — tracked separately.