Release v2.6.1 — _is_test_environment regression fix + structural cleanup¶
Release Date: 2026-06-23 Type: Patch Release (bug fix + reconciliation) Last Updated: 2026-06-23
Summary¶
v2.6.1 is a patch release centered on one operator-visible bug fix + the structural cleanup that followed.
The bug (_is_test_environment false-positive on numpy imports):
between 2026-02-06 (PR #398) and 2026-06-22 (commit
ce029849), a runtime "is this a test run?" heuristic in
src/podcast_scraper/config.py silently auto-flipped 24 config
defaults between TEST and PROD values. The heuristic looked at
"unittest" in sys.modules — but numpy lazy-imports numpy.testing
which pulls stdlib unittest into sys.modules. So every
production code path that imports numpy looked like a test
environment, silently routing model selection to TEST defaults.
5 months of eval runs, prod runs, and benchmarks all silently used the cheap models the bug routed them to.
What was actually affected¶
Of the 24 default-getters, 13 had TEST==PROD pairs (no-op flips — gemini-flash-lite, deepseek-chat, ollama llama3.1:8b for both tiers). The 10 that meaningfully flipped under the bug:
| Knob | TEST value (silent prod) | PROD value (now active) |
|---|---|---|
ner_model |
en_core_web_sm |
en_core_web_trf |
openai_summary_model |
gpt-4o-mini |
gpt-4o |
anthropic_speaker_model |
claude-haiku-4-5 |
claude-3-5-sonnet-20241022 |
anthropic_summary_model |
claude-haiku-4-5 |
claude-3-5-sonnet-20241022 |
mistral_speaker_model |
mistral-small-latest |
mistral-large-latest |
mistral_summary_model |
mistral-small-latest |
mistral-large-latest |
grok_speaker_model |
grok-beta |
grok-2 |
grok_summary_model |
grok-beta |
grok-2 |
local whisper_model |
tiny.en |
base.en |
summary_tokenize defaults |
safe minima | prod-mode registry tokenize |
Plus an incidental fix: .env file loading was silently skipped
in prod under the same false-positive (the helper ALSO gated .env
loading). Operators who relied on .env for API keys (rather than
shell exports) would have seen the file ignored. With the narrowed
detection, prod correctly loads .env; only pytest still skips it
(intentional hermeticity).
Same false-positive existed in
src/podcast_scraper/evaluation/autoresearch_track_a.py — meaning
.env.autoresearch silently failed to load during prod autoresearch
runs (operator-only AUTORESEARCH_* env-key overrides didn't actually
override). Fixed identically.
Operator action items after upgrade¶
- Cloud profile users whose pipelines fell through to defaults (rather than pinning specific models) will see their LLM model silently upgrade from the TEST tier to the PROD tier on the next pipeline run. Expect:
- Higher OpenAI cost for summarization (gpt-4o vs gpt-4o-mini — ~10-15× per-token cost difference)
- Higher Anthropic cost (claude-3-5-sonnet vs haiku — ~5-10×)
- Higher quality entity extraction (
en_core_web_trfvs_sm— measurably better PERSON recall, especially multi-token names) -
First-pipeline-run NER cold-start ~10-15s extra as
en_core_web_trfloads (same wheel is already pip-installed viapyproject.toml). -
Eval scoreboard caution: 256 frozen eval runs at
data/eval/runs/were captured during the bug window and used silent TEST defaults. Comparing pre-v2.6.1 scoreboards to post-v2.6.1 runs is not apples-to-apples for any of the 10 meaningfully-flipped knobs above. A_PRE_FIX_NOTE.mdis added todata/eval/runs/(this release) to flag this for future-you. -
.env.autoresearchoperators: this file actually loads now for prod autoresearch runs. Sanity-check what's in it; the override-.envsemantics are now real.
Structural cleanup landing alongside the fix¶
Per operator direction ("profiles play that role; we have dev vs prod profiles and no need for another set of controls"), the runtime test/prod auto-flipping mechanism is removed entirely:
- New profile YAML
config/profiles/test_default.yamlpins all the test-tier cheap models that the old TEST_DEFAULT_* constants used to provide via env-detect. tests/conftest.pysetsPODCAST_SCRAPER_PROFILE=test_defaultso tests inherit the cheap-model profile by default (the single hardcoded thing; everything else is profile-YAML-driven).- 22 of 24
_get_default_*helpers inconfig.pyare simplified to return the PROD constant directly. Profile YAMLs control TEST vs PROD via the merge order (registry < YAML < explicit kwargs). _is_test_environmentrenamed to_is_pytest_run. Its one remaining use is gating.envloading (hermeticity guard).Config._merge_profile_into_datafalls back toPODCAST_SCRAPER_PROFILEenv var when no explicitprofile=passed — enabling per-deployment profile selection without changing call sites.
Tests¶
Full unit test sweep: 4331 passed, 0 failed, 1 skipped.
Docs updates¶
docs/architecture/ARCHITECTURE.md§ "Language-aware processing" — NER selection clarified (profile-driven, not env-driven).docs/rfc/RFC-010-speaker-name-detection.mdlines 26 + 41 — same clarification.
Commit chain¶
ce029849(2026-06-22) — narrowed_is_test_environmentto stop the bleed (small targeted fix)d691610f(2026-06-23) — sibling fix inautoresearch_track_a.py+ ARCHITECTURE docc655cf11(2026-06-23) — structural cleanup: profiles as source of truth, 22 default-getters simplified, conftest wiring
Upgrade notes¶
Backwards compatible at the Python API surface
(Config, run_pipeline, service.run). Profiles previously
relying on the cheap-defaults silent flip will see model upgrades on
next run; explicit pins in operator-owned profile YAMLs are
unaffected (they always won over the env-detect).
No schema changes. No migration required.
Known follow-ups (separate from this release)¶
- Cloud profile explicit pinning audit (the 5 fall-through cloud
profiles in
config/profiles/) - Promotion of
test_defaultprofile to a fullProfilePresetin_PROFILE_PRESETS - Inlining the 22 trivial
_get_default_*helpers directly asField(default=PROD_DEFAULT_X)in each Pydantic field - TEST_DEFAULT_ vs PROD_DEFAULT_ constant consolidation in
config_constants.pyfor the 13 TEST==PROD pairs
These are hygiene work, not blocking.