From 6defe7eb6c462bb784d1f27f5afe7ca4b627fc70 Mon Sep 17 00:00:00 2001 From: Gille <4317663+helix4u@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:55:19 -0600 Subject: [PATCH] fix(config): preserve lossy decimal values as strings --- hermes_cli/config.py | 13 ++++++++++++- tests/hermes_cli/test_config_set_coercion.py | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 32d6cbf936..e563d137fc 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -15,6 +15,7 @@ This module provides: """ import copy +from decimal import Decimal, InvalidOperation from hermes_cli.cli_output import line_input import json import logging @@ -5434,7 +5435,12 @@ def _coerce_int(value: str): def _coerce_float(value: str): - """Return float(value) for a clean float literal, else None.""" + """Return ``float(value)`` when conversion preserves its decimal value. + + Decimal-looking identifiers can be much more precise than a binary float. + Silently rounding one here corrupts it before it reaches ``config.yaml``, + so values that do not round-trip through ``float`` remain strings. + """ try: f = float(value) except (TypeError, ValueError): @@ -5443,6 +5449,11 @@ def _coerce_float(value: str): # and round-trip confusingly through YAML. if f != f or f in (float("inf"), float("-inf")): return None + try: + if Decimal(value) != Decimal(str(f)): + return None + except InvalidOperation: + return None return f diff --git a/tests/hermes_cli/test_config_set_coercion.py b/tests/hermes_cli/test_config_set_coercion.py index 821d3c7d4e..53518a5910 100644 --- a/tests/hermes_cli/test_config_set_coercion.py +++ b/tests/hermes_cli/test_config_set_coercion.py @@ -42,6 +42,18 @@ class TestNumericCoercion: v = _read(tmp_path, "agent", "max_turns") assert v == -2.5 and isinstance(v, float) + def test_lossy_decimal_identifier_stays_string(self, tmp_path, monkeypatch): + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + client_id = "123456789012.98765432109876" + + cfg.set_config_value("mcp_servers.example.oauth.client_id", client_id) + + saved = _read( + tmp_path, "mcp_servers", "example", "oauth", "client_id" + ) + assert saved == client_id + assert isinstance(saved, str) + class TestNullCoercion: @pytest.mark.parametrize("token", ["null", "none", "None", "~"])