I set EMBED_DEVICE=cuda in .env. The pipeline ran on CPU. I set CHUNK_CHUNK_SIZE=1024. It chunked at 512. The .env file was being read by something, but the nested settings classes ignored it entirely and used their hardcoded defaults.
// 01 — THE SETUP
Config is organized into nested Pydantic BaseSettings classes by concern: EmbeddingConfig, ChunkingConfig, and ChromaConfig, each with its own env prefix (EMBED_, CHUNK_, CHROMA_).
// 02 — THE SYMPTOM
Environment variables in .env had no effect. No error, no warning. The pipeline simply behaved as if the file didn’t exist, falling back to defaults for every nested setting.
// 03 — THE CULPRIT
Pydantic Settings only reads a .env file when a class is explicitly told to. The nested classes declared their env prefixes but never declared env_file=".env", so they happily read from actual environment variables but never opened the file. Without that one directive, .env is invisible to the class. Each nested settings class is its own settings source and needs its own configuration; setting it on the parent doesn’t cascade.
// 04 — THE FIX
Give every nested class its full config:
model_config = SettingsConfigDict(
env_prefix="EMBED_",
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
With env_file declared on each nested class, the .env values finally loaded.
TAKEAWAYS
- Pydantic Settings reads
.envonly whenenv_fileis set, and nested settings classes each need it. It doesn’t inherit from the parent. - A config that silently falls back to defaults is a trap: nothing tells you your settings aren’t being read. Test that a non-default value actually takes effect.
- “It reads environment variables but not the file” is the tell: the file directive is missing, not the prefix.
NEXT
- Anomaly log: structlog is not logging: the %s trap.
