Skip to content

Commit ba54e38

Browse files
authored
fix(cli): tolerate BOM in ov config (#1922)
1 parent 6c58cb1 commit ba54e38

4 files changed

Lines changed: 39 additions & 3 deletions

File tree

openviking_cli/doctor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def _find_config() -> Optional[Path]:
5454
def _load_config_json(config_path: Path) -> Optional[dict]:
5555
"""Parse ov.conf as JSON. Returns None if the file is unreadable or not valid JSON."""
5656
try:
57-
raw = config_path.read_text(encoding="utf-8")
57+
raw = config_path.read_text(encoding="utf-8-sig")
5858
raw = os.path.expandvars(raw)
5959
return json.loads(raw)
6060
except (OSError, json.JSONDecodeError):
@@ -72,7 +72,7 @@ def check_config() -> tuple[bool, str, Optional[str]]:
7272
)
7373

7474
try:
75-
raw = config_path.read_text(encoding="utf-8")
75+
raw = config_path.read_text(encoding="utf-8-sig")
7676
raw = os.path.expandvars(raw)
7777
data = json.loads(raw)
7878
except json.JSONDecodeError as exc:

openviking_cli/utils/config/open_viking_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ def _load_from_file(cls, config_file: str) -> "OpenVikingConfig":
388388
if not config_path.exists():
389389
raise FileNotFoundError(f"Config file does not exist: {config_file}")
390390

391-
with open(config_path, "r", encoding="utf-8") as f:
391+
with open(config_path, "r", encoding="utf-8-sig") as f:
392392
raw = f.read()
393393

394394
# Expand $VAR and ${VAR} inside the JSON text (useful for container deployments).

tests/cli/test_doctor.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ def test_pass_with_valid_config(self, tmp_path: Path):
3232
assert ok
3333
assert str(config) in detail
3434

35+
def test_pass_with_utf8_bom_config(self, tmp_path: Path):
36+
config = tmp_path / "ov.conf"
37+
config.write_text("\ufeff" + json.dumps({"embedding": {"dense": {}}}))
38+
with patch("openviking_cli.doctor._find_config", return_value=config):
39+
ok, detail, fix = check_config()
40+
assert ok
41+
assert str(config) in detail
42+
3543
def test_fail_missing_config(self):
3644
with patch("openviking_cli.doctor._find_config", return_value=None):
3745
ok, detail, fix = check_config()

tests/test_config_loader.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,34 @@ def test_openviking_config_singleton_preserves_value_error_for_bad_config(tmp_pa
233233
OpenVikingConfigSingleton.reset_instance()
234234

235235

236+
def test_openviking_config_singleton_loads_utf8_bom_config(tmp_path, monkeypatch):
237+
monkeypatch.setenv(OPENVIKING_CONFIG_ENV, "/tmp/codex-no-config.json")
238+
239+
from openviking_cli.utils.config import open_viking_config as config_module
240+
241+
class _ConfigStub:
242+
default_account = "default"
243+
244+
loaded = {}
245+
246+
def _from_dict(data):
247+
loaded.update(data)
248+
return _ConfigStub()
249+
250+
monkeypatch.setattr(config_module.OpenVikingConfig, "from_dict", _from_dict)
251+
252+
config_path = tmp_path / "ov.conf"
253+
config_path.write_text("\ufeff{}", encoding="utf-8")
254+
255+
config_module.OpenVikingConfigSingleton.reset_instance()
256+
config = config_module.OpenVikingConfigSingleton.initialize(config_path=str(config_path))
257+
258+
assert config.default_account == "default"
259+
assert loaded == {}
260+
261+
config_module.OpenVikingConfigSingleton.reset_instance()
262+
263+
236264
def test_require_config_missing_message_uses_openviking_ai_docs(tmp_path, monkeypatch):
237265
import openviking_cli.utils.config.config_loader as loader
238266

0 commit comments

Comments
 (0)