Skip to main content

lru-cached-settings-pin-env-across-test-modules

An `@lru_cache` settings factory is a process-wide singleton, so the FIRST test module to touch it pins its monkeypatched env for the whole run. A second module setting a different secret/URL gets the first module's value — symptom is "passes alone, fails when run together" with a signature/connection error that points at the wrong thing.

ソース情報

リポジトリ
blas1n/claude-skills
ソースの最終更新活動
2026年8月8日 16:53
検出された SKILL.md の言語
英語
スター
2
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
lru-cached-settings-pin-env-across-test-modules
description
An `@lru_cache` settings factory is a process-wide singleton, so the FIRST test module to touch it pins its monkeypatched env for the whole run. A second module setting a different secret/URL gets the first module's value — symptom is "passes alone, fails when run together" with a signature/connection error that points at the wrong thing.
category
trap
# `@lru_cache`d settings pin env across test modules ## Problem Config factories are cached so construction cost is paid once: ```python @lru_cache(maxsize=1) def get_settings() -> Settings: """Return the process-wide Settings singleton (cached).""" return Settings() # reads os.environ via pydantic-settings ``` Two test modules each set their own secret and mint tokens with it: ```python # tests/api/test_a.py monkeypatch.setenv("USER_JWT_SECRET", "test-session-secret") # tests/mcp/test_b.py monkeypatch.setenv("USER_JWT_SECRET", "e2e-session-secret") ``` Whichever module runs first populates the cache. The second signs with its own secret while the server still verifies with the first one: ``` {"detail":"user JWT verification failed: Signature verification failed"} ``` The tells: - **Passes alone, fails together.** `pytest tests/api/test_a.py` is green; `pytest tests/mcp/test_b.py tests/api/test_a.py` is red. Run order decides which module wins, so it can also flip when you add an unrelated test file. - **The error blames the wrong layer.** "Signature verification failed" reads as a bug in token minting or key handling, not as config caching. - `monkeypatch` is doing its job perfectly — `os.environ` *is* correct. Nothing re-reads it. Same shape for any cached factory: a DB URL, an issuer, a feature flag, an API base. Anything behind `@lru_cache` / a module-level singleton. ## Solution ### Clear every cached factory the module influences, on both edges Not just the obvious one. A request often touches several settings objects (app config, authz config, worker config), each independently cached: ```python @pytest_asyncio.fixture async def db(monkeypatch): monkeypatch.setenv("USER_JWT_SECRET", SESSION_SECRET) monkeypatch.setenv("BSVIBE_OAUTH_ISSUER", ISSUER) get_settings.cache_clear() # authz Settings is @lru_cache(maxsize=1): whichever module touches it first # would otherwise pin its USER_JWT_SECRET for the whole process. get_authz_settings.cache_clear() yield ... get_settings.cache_clear() # clear on teardown too — the NEXT get_authz_settings.cache_clear() # module inherits whatever you left ``` Clearing only on setup leaves your value pinned for everyone after you. ### Find the cached factories ```bash grep -rn "lru_cache\|@cache" backend/ --include="*.py" | grep -B2 -i "settings\|config" ``` Then confirm which ones the code path under test actually reads — the import site tells you: `from x.settings import get_settings as get_authz_settings`. ### Diagnose an existing flake If a test passes alone and fails in a suite, bisect by pairing rather than reading the failing test: ```bash pytest tests/suspect_module.py tests/failing_module.py::test_x -q ``` A pass alone plus a fail when paired is near-conclusive for shared process state; cached settings are the first place to look. ## Related - `env-gated-test-backend-local-red-not-ci-red` - `pytest-singleton-async-resource-cross-loop-leak` - `feedback_drift_guard_cache_stale`
GitHubで見る