Skip to content

Commit eb8c1e7

Browse files
authored
fix: stop dedup from deleting the entire env config section (#259)
The structured->flat mapping 'env.name -> env' has a flat key that is identical to the section name. Both _resolve_layer_format_duplicates and _drop_base_keys_overridden_by_layer treated that as a signal to pop the whole 'env' block, silently dropping env.name, env.split_dir, env.skill_init, and every other environment setting from structured configs during load. Skip the section-name collision case in both helpers so the env section survives layer dedup and child inheritance. Add regression tests covering base-only, child-inheritance, flatten, and the shipped searchqa config.
1 parent a80fc59 commit eb8c1e7

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

‎skillopt/config.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,20 @@ def _resolve_layer_format_duplicates(cfg: dict) -> None:
221221
"""Prefer canonical structured keys over equivalent flat keys in a layer."""
222222
for dotted, flat_key in _FLATTEN_MAP.items():
223223
if _nested_key_present(cfg, dotted):
224+
# `env.name -> env` maps onto the section name itself: popping it
225+
# would delete the whole env section. Skip that case.
226+
if flat_key == dotted.split(".", 1)[0]:
227+
continue
224228
cfg.pop(flat_key, None)
225229

226230

227231
def _drop_base_keys_overridden_by_layer(base: dict, override: dict) -> None:
228232
"""Honor child precedence when inheritance mixes flat and structured YAML."""
229233
for dotted, flat_key in _FLATTEN_MAP.items():
234+
if flat_key == dotted.split(".", 1)[0]:
235+
# `env.name -> env` maps onto the section name itself: dropping
236+
# it would delete the whole section instead of one key.
237+
continue
230238
if flat_key in override or _nested_key_present(override, dotted):
231239
base.pop(flat_key, None)
232240
_remove_nested_key(base, dotted)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""Regression: the ``env`` section must survive layer-format dedup.
2+
3+
``_resolve_layer_format_duplicates`` pops a flat key whenever the
4+
equivalent structured key is present. For the mapping ``env.name -> env``
5+
the flat key is the section name itself, so popping it deleted the entire
6+
``env`` block from every structured config (base and child) before
7+
inheritance — ``env.name``, ``env.split_dir``, ``env.skill_init``, etc.
8+
were silently lost, which broke ``load_config`` for every environment.
9+
10+
The same section-name collision also applied to
11+
``_drop_base_keys_overridden_by_layer``, which deleted a base's whole
12+
``env`` section whenever a child overrode ``env.name``.
13+
"""
14+
15+
from __future__ import annotations
16+
17+
from skillopt.config import _load_yaml, flatten_config, load_config
18+
19+
20+
def test_env_section_survives_dedup_in_base(tmp_path):
21+
base = tmp_path / "base.yaml"
22+
base.write_text(
23+
"env:\n"
24+
" name: searchqa\n"
25+
" split_mode: split_dir\n"
26+
" split_dir: data/searchqa_split\n"
27+
" workers: 24\n",
28+
encoding="utf-8",
29+
)
30+
cfg = load_config(str(base))
31+
assert cfg["env"]["name"] == "searchqa"
32+
assert cfg["env"]["split_dir"] == "data/searchqa_split"
33+
34+
35+
def test_env_section_survives_child_inheritance(tmp_path):
36+
base = tmp_path / "base.yaml"
37+
base.write_text(
38+
"env:\n"
39+
" name: base\n"
40+
" split_mode: ratio\n"
41+
" workers: 4\n",
42+
encoding="utf-8",
43+
)
44+
child = tmp_path / "child.yaml"
45+
child.write_text(
46+
"_base_: base.yaml\n"
47+
"env:\n"
48+
" name: pricewatch\n"
49+
" split_mode: split_dir\n"
50+
" split_dir: data/pricewatch_split\n",
51+
encoding="utf-8",
52+
)
53+
cfg = load_config(str(child))
54+
assert cfg["env"]["name"] == "pricewatch"
55+
assert cfg["env"]["split_mode"] == "split_dir"
56+
assert cfg["env"]["split_dir"] == "data/pricewatch_split"
57+
# Inherited env key is preserved alongside the child overrides.
58+
assert cfg["env"]["workers"] == 4
59+
60+
61+
def test_flatten_config_keeps_env_keys(tmp_path):
62+
config = tmp_path / "c.yaml"
63+
config.write_text(
64+
"env:\n"
65+
" name: pricewatch\n"
66+
" split_dir: data/pricewatch_split\n"
67+
" max_completion_tokens: 2048\n",
68+
encoding="utf-8",
69+
)
70+
flat = flatten_config(load_config(str(config)))
71+
assert flat["env"] == "pricewatch"
72+
assert flat["split_dir"] == "data/pricewatch_split"
73+
assert flat["max_completion_tokens"] == 2048
74+
75+
76+
def test_shipped_searchqa_config_still_loads_env():
77+
# Guard against regressions in the repo's own environment configs.
78+
cfg = _load_yaml("configs/searchqa/default.yaml")
79+
assert cfg["env"]["name"] == "searchqa"
80+
assert cfg["env"]["split_dir"] == "data/searchqa_split"

0 commit comments

Comments
 (0)