diff --git a/CHANGELOG.md b/CHANGELOG.md index a55a12d0..b3cc494a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ breaking changes may land in a minor release. ### Changed +- Warn when `scm.max_parallel > 1` is configured; keep the value clamped to 1 + until parallel fan-out is built (#229). - Register hooks through the installed `bmad-loop relay ` command. Upgrading invalidates Codex hook trust: Codex re-prompts at the next launch, and hooks silently do not fire until the new commands are accepted. Re-run `bmad-loop init` to migrate diff --git a/src/bmad_loop/policy.py b/src/bmad_loop/policy.py index 319af246..e38d982a 100644 --- a/src/bmad_loop/policy.py +++ b/src/bmad_loop/policy.py @@ -1125,6 +1125,14 @@ def loads(text: str, plugin_schemas: dict[str, Any] | None = None) -> Policy: requested_parallel = _typed_int(scm_d, "scm", "max_parallel", ScmPolicy.max_parallel) if requested_parallel < 1: raise PolicyError(f"scm.max_parallel must be >= 1: got {requested_parallel}") + if requested_parallel > 1: + warnings.warn( + f"scm.max_parallel = {requested_parallel} is configured, but parallel " + "fan-out (Phase 5) is not built yet: the value is clamped to 1 and has " + "no effect (see #229).", + UserWarning, + stacklevel=3, + ) # This one was strict before its sibling int knobs were (a TOML `true`, with # int(True) == 1, or a `1.9` coercing through int() would silently shrink a # safety-net budget); `_typed_int` is that same guard, message included. diff --git a/tests/test_policy.py b/tests/test_policy.py index 1cadd597..342e87ef 100644 --- a/tests/test_policy.py +++ b/tests/test_policy.py @@ -1277,6 +1277,24 @@ def test_scm_max_parallel_clamped_to_one(tmp_path): policy.load(p) +def test_scm_max_parallel_above_one_warns(): + """Phase 5 parallel fan-out (#229) is unbuilt, so a configured value > 1 is + still silently inert without this warning -- an operator setting + ``max_parallel = 4`` would otherwise get no signal that it has no effect. + + Ablation: delete the ``requested_parallel > 1`` warning block in ``loads()``; + this test fails because no warning fires while the clamp still applies.""" + with pytest.warns(UserWarning, match=r"scm\.max_parallel"): + loaded = policy.loads("[scm]\nmax_parallel = 4\n") + assert loaded.scm.max_parallel == 1 + + +def test_scm_max_parallel_equal_to_one_does_not_warn(recwarn): + loaded = policy.loads("[scm]\nmax_parallel = 1\n") + assert loaded.scm.max_parallel == 1 + assert len(recwarn) == 0 + + def test_scm_preserve_keep_settings(tmp_path): p = tmp_path / "policy.toml" p.write_text("[scm]\npreserve_keep = 5\n")