Skip to content

background_tasks: BackgroundTasksSettings silently reads unprefixed env vars (broker_url, result_backend, …), contradicting its own docstring #283

Description

@antosubash

Summary

BackgroundTasksSettings is documented as no longer reading environment variables at construction. It still does — but under unprefixed names (broker_url, result_backend, retention_days, max_retries, …), because the class subclasses pydantic-settings BaseSettings without an env_prefix.

The result is the opposite of the docstring's intent: instead of reading SM_BG_TASKS_*, it reads a set of very generic names that are easy to collide with in a container, and those silently win over the DB-hydrated values the module is supposed to be configured by.

The mismatch

background_tasks/settings.py opens with:

Construction no longer reads SM_BG_TASKS_* environment variables. Values come from pydantic defaults at boot, then get hydrated from the DB by the hosting lifespan before module on_startup runs.

but declares:

classBackgroundTasksSettings(BaseSettings):
model_config=SettingsConfigDict(extra="ignore")
broker_url: str=Field(default=DEFAULT_BROKER_URL, json_schema_extra=_CELERY_RESTART)
result_backend: str=Field(default=DEFAULT_RESULT_BACKEND, json_schema_extra=_CELERY_RESTART)
...

With no env_prefix, pydantic-settings resolves each field from the bare, case-insensitive field name. So broker_url, result_backend, task_default_queue, stuck_after_seconds, purge_interval_seconds, retention_days and max_retries are all live environment reads.

There's an internal inconsistency too: one field does read a prefixed name explicitly —

task_always_eager: bool=env_bool("SM_BG_TASKS_TASK_ALWAYS_EAGER")

— so a single class mixes one SM_BG_TASKS_-prefixed field with nine unprefixed ones.

Confirmed behaviour

Booting a host with SM_ENVIRONMENT=staging and no overrides fails the production guard, as expected:

ValidationError: 1 validation error for BackgroundTasksSettings
Value error, broker_url, result_backend must not point at localhost when
SM_ENVIRONMENT='staging'.

Adding bare entries to .env (which the host loads into os.environ) makes it pass:

broker_url=redis://redis:6379/8
result_backend=redis://redis:6379/9

Adding the documented SM_BG_TASKS_BROKER_URL / SM_BG_TASKS_RESULT_BACKEND instead does not satisfy it — those names reach seed_dev_settings.py and the worker's _assert_broker_isolated, but never the settings class itself.

Why it matters in practice

A production deployment of a downstream app (Dokploy) crash-looped on this validator. The fix that actually worked was setting bare broker_url / result_backend env vars on the container — i.e. depending on this accidental behaviour, under names generic enough that another component setting broker_url for its own purposes would silently reconfigure Celery.

It also makes the DB the source of truth only when nobody happens to have those names in the environment, which is hard to reason about from the outside.

The validator message compounds it: it says "Set these to the Redis service host (e.g. redis://redis:6379/0)" without naming a mechanism, so the natural first guess is the documented SM_BG_TASKS_* prefix, which has no effect.

Suggested fix

Pick whichever matches the intent:

  • If env should be ignored (what the docstring says): stop inheriting env parsing for these fields, or set model_config = SettingsConfigDict(extra="ignore", env_prefix="SM_BG_TASKS_") so at least the names are namespaced and match the docs, the seed script, and _assert_broker_isolated.
  • If env should be honoured: make it SM_BG_TASKS_-prefixed for every field, and drop the one-off env_bool on task_always_eager so the class has a single rule.

Either way, it would help if the localhost validator named the mechanism it expects — settings row, or a specific env var — since that error is most people's first encounter with this.

Version

simple_module_background_tasks 0.0.32.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions