{"repo":"NousResearch/hermes-agent","public_id":"10890","display_label":"Preserve YAML comments and formatting in config writes","summary":"These issues and PRs address a recurring problem where config write operations (particularly `hermes config set`/`unset`) strip comments, formatting, and template blocks from config.yaml. Several entries also concern YAML serialization inconsistencies and list indentation across multiple serializers.","subsystem":"gateway","counts":{"members":16,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":5,"merged_prs":3,"attempt_prs":8},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":22845},"resolution":{"state":"partially_resolved","by":"pr-22845,pr-52386,pr-118918"},"freshness":{"state":"verified","last_verified_at":"2026-09-22T08:08:00+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.1263333333333333},"fix_edges":[{"issue_number":10890,"pr_number":22845,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":31999,"pr_number":52386,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":50698,"pr_number":118918,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":63039,"pr_number":118918,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":92554,"pr_number":118918,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":107511,"pr_number":118918,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":109611,"pr_number":118918,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":10890,"title":"fix: Preserve comments and Unicode in config.yaml when saving settings","url":null,"state":"closed"},{"kind":"issue","number":10892,"title":"refactor: atomic_yaml_write naming and implementation mismatch","url":null,"state":"closed"},{"kind":"issue","number":31999,"title":"YAML serialization inconsistency across 3 serializers causes config corruption and Gateway crash","url":null,"state":"closed"},{"kind":"issue","number":50698,"title":"hermes config set strips all comments and commented-out template blocks from config.yaml","url":null,"state":"closed"},{"kind":"issue","number":63039,"title":"config: set_config_value drops comments/formatting — diverges from save_config_value","url":null,"state":"closed"},{"kind":"pr","number":22717,"title":"fix(cli): preserve config comments on setting writes","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#22845","number":22845}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":22845,"title":"fix(cli): preserve config comments on setting writes (salvage #22717)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cli.py","pyproject.toml","scripts/release.py","tests/cli/test_cli_save_config_value.py","utils.py"],"fix_hunks":[{"file":"cli.py","hunks":["@@ -2206,26 +2206,10 @@ def save_config_value(key_path: str, value: any) -> bool:\n         # Ensure parent directory exists (for ~/.hermes/config.yaml on first use)\n         config_path.parent.mkdir(parents=True, exist_ok=True)\n         \n-        # Load existing config\n-        if config_path.exists():\n-            with open(config_path, 'r', encoding=\"utf-8\") as f:\n-                config = yaml.safe_load(f) or {}\n-        else:\n-            config = {}\n-        \n-        # Navigate to the key and set value\n-        keys = key_path.split('.')\n-        current = config\n-        for key in keys[:-1]:\n-            if key not in current or not isinstance(current[key], dict):\n-                current[key] = {}\n-            current = current[key]\n-        current[keys[-1]] = value\n-        \n-        # Save back atomically — write to temp file + fsync + os.replace\n-        # so an interrupt never leaves config.yaml truncated or empty.\n-        from utils import atomic_yaml_write\n-        atomic_yaml_write(config_path, config)\n+        # Save back atomically while preserving comments, ordering, quotes, and\n+        # readable Unicode in user-edited config.yaml.\n+        from utils import atomic_roundtrip_yaml_update\n+        atomic_roundtrip_yaml_update(config_path, key_path, value)\n         \n         # Enforce owner-only permissions on config files (contain API keys)\n         try:"]},{"file":"pyproject.toml","hunks":["@@ -20,6 +20,7 @@ dependencies = [\n   \"rich>=14.3.3,<15\",\n   \"tenacity>=9.1.4,<10\",\n   \"pyyaml>=6.0.2,<7\",\n+  \"ruamel.yaml>=0.18.16,<0.19\",\n   \"requests>=2.33.0,<3\",  # CVE-2026-25645\n   \"jinja2>=3.1.5,<4\",\n   \"pydantic>=2.12.5,<3\","]},{"file":"scripts/release.py","hunks":["@@ -67,6 +67,7 @@\n     \"[redacted:email]\": \"kidonng\",\n     \"[redacted:email]\": \"DanielLSM\",\n     \"[redacted:email]\": \"v1b3coder\",\n+    \"[redacted:email]\": \"ming1523\",\n     \"[redacted:email]\": \"Qwinty\",\n     \"[redacted:email]\": \"CalmProton\",\n     \"[redacted:email]\": \"mbac\","]},{"file":"tests/cli/test_cli_save_config_value.py","hunks":["@@ -1,15 +1,13 @@\n \"\"\"Tests for save_config_value() in cli.py — atomic write behavior.\"\"\"\n \n-import os\n import yaml\n-from pathlib import Path\n-from unittest.mock import patch, MagicMock\n+from unittest.mock import MagicMock\n \n import pytest\n \n \n class TestSaveConfigValueAtomic:\n-    \"\"\"save_config_value() must use atomic_yaml_write to avoid data loss.\"\"\"\n+    \"\"\"save_config_value() must use atomic round-trip YAML updates.\"\"\"\n \n     @pytest.fixture\n     def config_env(self, tmp_path, monkeypatch):","@@ -24,18 +22,15 @@ def config_env(self, tmp_path, monkeypatch):\n         monkeypatch.setattr(\"cli._hermes_home\", hermes_home)\n         return config_path\n \n-    def test_calls_atomic_yaml_write(self, config_env, monkeypatch):\n-        \"\"\"save_config_value must route through atomic_yaml_write, not bare open().\"\"\"\n-        mock_atomic = MagicMock()\n-        monkeypatch.setattr(\"utils.atomic_yaml_write\", mock_atomic)\n+    def test_calls_roundtrip_yaml_update(self, config_env, monkeypatch):\n+        \"\"\"save_config_value must preserve user-edited YAML structure.\"\"\"\n+        mock_update = MagicMock()\n+        monkeypatch.setattr(\"utils.atomic_roundtrip_yaml_update\", mock_update)\n \n         from cli import save_config_value\n         save_config_value(\"display.skin\", \"mono\")\n \n-        mock_atomic.assert_called_once()\n-        written_path, written_data = mock_atomic.call_args[0]\n-        assert Path(written_path) == config_env\n-        assert written_data[\"display\"][\"skin\"] == \"mono\"\n+        mock_update.assert_called_once_with(config_env, \"display.skin\", \"mono\")\n \n     def test_preserves_existing_keys(self, config_env):\n         \"\"\"Writing a new key must not clobber existing config entries.\"\"\"","@@ -82,6 +77,47 @@ def test_preserves_env_ref_templates_in_unrelated_fields(self, config_env):\n         assert result[\"model\"][\"default\"] == \"doubao-pro\"\n         assert result[\"custom_providers\"][0][\"api_key\"] == \"${TU_ZI_API_KEY}\"\n \n+    def test_preserves_comments_after_config_mutation(self, config_env):\n+        \"\"\"CLI config writes should not strip existing user comments.\"\"\"\n+        config_env.write_text(\n+            \"# user selected model\\n\"\n+            \"model:\\n\"\n+            \"  # keep this provider note\\n\"\n+            \"  provider: openrouter\\n\"\n+            \"display:\\n\"\n+            \"  skin: default  # inline skin note\\n\",\n+            encoding=\"utf-8\",\n+        )\n+\n+        from cli import save_config_value\n+        save_config_value(\"display.skin\", \"mono\")\n+\n+        text = config_env.read_text(encoding=\"utf-8\")\n+        result = yaml.safe_load(text)\n+        assert result[\"display\"][\"skin\"] == \"mono\"\n+        assert \"# user selected model\" in text\n+        assert \"# keep this provider note\" in text\n+        assert \"# inline skin note\" in text\n+\n+    def test_preserves_readable_unicode_after_config_mutation(self, config_env):\n+        \"\"\"Non-ASCII prompts should remain readable instead of \\\\u-escaped.\"\"\"\n+        config_env.write_text(\n+            \"agent:\\n\"\n+            \"  system_prompt: 你好，保持中文输出\\n\"\n+            \"display:\\n\"\n+            \"  skin: default\\n\",\n+            encoding=\"utf-8\",\n+        )\n+\n+        from cli import save_config_value\n+        save_config_value(\"display.skin\", \"mono\")\n+\n+        text = config_env.read_text(encoding=\"utf-8\")\n+        result = yaml.safe_load(text)\n+        assert result[\"agent\"][\"system_prompt\"] == \"你好，保持中文输出\"\n+        assert \"你好，保持中文输出\" in text\n+        assert \"\\\\u4f60\" not in text\n+\n     def test_file_not_truncated_on_error(self, config_env, monkeypatch):\n         \"\"\"If atomic_yaml_write raises, the original file is untouched.\"\"\"\n         original_content = config_env.read_text()","@@ -89,7 +125,7 @@ def test_file_not_truncated_on_error(self, config_env, monkeypatch):\n         def exploding_write(*args, **kwargs):\n             raise OSError(\"disk full\")\n \n-        monkeypatch.setattr(\"utils.atomic_yaml_write\", exploding_write)\n+        monkeypatch.setattr(\"utils.atomic_roundtrip_yaml_update\", exploding_write)\n \n         from cli import save_config_value\n         result = save_config_value(\"display.skin\", \"broken\")"]},{"file":"utils.py","hunks":["@@ -188,6 +188,70 @@ def atomic_yaml_write(\n         raise\n \n \n+def atomic_roundtrip_yaml_update(\n+    path: Union[str, Path],\n+    key_path: str,\n+    value: Any,\n+) -> None:\n+    \"\"\"Update one dotted YAML key while preserving comments and readable text.\n+\n+    This is intentionally narrower than :func:`atomic_yaml_write`: it is for\n+    user-edited config files where comments, ordering, quoting, and Unicode\n+    should survive a single setting mutation.  Writes still use the same temp\n+    file + fsync + atomic replace pattern.\n+    \"\"\"\n+    from ruamel.yaml import YAML\n+    from ruamel.yaml.comments import CommentedMap\n+\n+    path = Path(path)\n+    path.parent.mkdir(parents=True, exist_ok=True)\n+\n+    yaml_rt = YAML(typ=\"rt\")\n+    yaml_rt.preserve_quotes = True\n+    yaml_rt.allow_unicode = True\n+    yaml_rt.default_flow_style = False\n+    yaml_rt.indent(mapping=2, sequence=4, offset=2)\n+\n+    if path.exists():\n+        with path.open(\"r\", encoding=\"utf-8\") as f:\n+            config = yaml_rt.load(f) or CommentedMap()\n+    else:\n+        config = CommentedMap()\n+\n+    if not isinstance(config, CommentedMap):\n+        config = CommentedMap(config)\n+\n+    current = config\n+    keys = key_path.split(\".\")\n+    for key in keys[:-1]:\n+        next_value = current.get(key)\n+        if not isinstance(next_value, CommentedMap):\n+            next_value = CommentedMap()\n+            current[key] = next_value\n+        current = next_value\n+    current[keys[-1]] = value\n+\n+    original_mode = _preserve_file_mode(path)\n+    fd, tmp_path = tempfile.mkstemp(\n+        dir=str(path.parent),\n+        prefix=f\".{path.stem}_\",\n+        suffix=\".tmp\",\n+    )\n+    try:\n+        with os.fdopen(fd, \"w\", encoding=\"utf-8\") as f:\n+            yaml_rt.dump(config, f)\n+            f.flush()\n+            os.fsync(f.fileno())\n+        real_path = atomic_replace(tmp_path, path)\n+        _restore_file_mode(real_path, original_mode)\n+    except BaseException:\n+        try:\n+            os.unlink(tmp_path)\n+        except OSError:\n+            pass\n+        raise\n+\n+\n # ─── JSON Helpers ─────────────────────────────────────────────────────────────\n \n "]},{"file":"uv.lock","hunks":["@@ -1976,6 +1976,7 @@ dependencies = [\n     { name = \"pyyaml\" },\n     { name = \"requests\" },\n     { name = \"rich\" },\n+    { name = \"ruamel-yaml\" },\n     { name = \"tenacity\" },\n     { name = \"tzdata\", marker = \"sys_platform == 'win32'\" },\n ]","@@ -2265,6 +2266,7 @@ requires-dist = [\n     { name = \"qrcode\", marker = \"extra == 'messaging'\", specifier = \">=7.0,<8\" },\n     { name = \"requests\", specifier = \">=2.33.0,<3\" },\n     { name = \"rich\", specifier = \">=14.3.3,<15\" },\n+    { name = \"ruamel-yaml\", specifier = \">=0.18.16,<0.19\" },\n     { name = \"ruff\", marker = \"extra == 'dev'\" },\n     { name = \"simple-term-menu\", marker = \"extra == 'cli'\", specifier = \">=1.0,<2\" },\n     { name = \"slack-bolt\", marker = \"extra == 'messaging'\", specifier = \">=1.18.0,<2\" },","@@ -4912,6 +4914,66 @@ wheels = [\n     { url = \"https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl\", hash = \"sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e\", size = 562191, upload-time = \"2025-11-30T20:24:36.853Z\" },\n ]\n \n+[[package]]\n+name = \"ruamel-yaml\"\n+version = \"0.18.17\"\n+source = { registry = \"https://pypi.org/simple\" }\n+dependencies = [\n+    { name = \"ruamel-yaml-clib\", marker = \"python_full_version < '3.15' and platform_python_implementation == 'CPython'\" },\n+]\n+sdist = { url = \"https://files.pythonhosted.org/packages/3a/2b/7a1f1ebcd6b3f14febdc003e658778d81e76b40df2267904ee6b13f0c5c6/ruamel_yaml-0.18.17.tar.gz\", hash = \"sha256:9091cd6e2d93a3a4b157ddb8fabf348c3de7f1fb1381346d985b6b247dcd8d3c\", size = 149602, upload-time = \"2025-12-17T20:02:55.757Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/af/fe/b6045c782f1fd1ae317d2a6ca1884857ce5c20f59befe6ab25a8603c43a7/ruamel_yaml-0.18.17-py3-none-any.whl\", hash = \"sha256:9c8ba9eb3e793efdf924b60d521820869d5bf0cb9c6f1b82d82de8295e290b9d\", size = 121594, upload-time = \"2025-12-17T20:02:07.657Z\" },\n+]\n+\n+[[package]]\n+name = \"ruamel-yaml-clib\"\n+version = \"0.2.15\"\n+source = { registry = \"https://pypi.org/simple\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz\", hash = \"sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600\", size = 225794, upload-time = \"2025-11-16T16:12:59.761Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/2c/80/8ce7b9af532aa94dd83360f01ce4716264db73de6bc8efd22c32341f6658/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl\", hash = \"sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd\", size = 147998, upload-time = \"2025-11-16T16:13:13.241Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/53/09/de9d3f6b6701ced5f276d082ad0f980edf08ca67114523d1b9264cd5e2e0/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl\", hash = \"sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137\", size = 132743, upload-time = \"2025-11-16T16:13:14.265Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/0e/f7/73a9b517571e214fe5c246698ff3ed232f1ef863c8ae1667486625ec688a/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl\", hash = \"sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401\", size = 731459, upload-time = \"2025-11-16T20:22:44.338Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/9b/a2/0dc0013169800f1c331a6f55b1282c1f4492a6d32660a0cf7b89e6684919/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262\", size = 749289, upload-time = \"2025-11-16T16:13:15.633Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/aa/ed/3fb20a1a96b8dc645d88c4072df481fe06e0289e4d528ebbdcc044ebc8b3/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f\", size = 777630, upload-time = \"2025-11-16T16:13:16.898Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/60/50/6842f4628bc98b7aa4733ab2378346e1441e150935ad3b9f3c3c429d9408/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_aarch64.whl\", hash = \"sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d\", size = 744368, upload-time = \"2025-11-16T16:13:18.117Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/d3/b0/128ae8e19a7d794c2e36130a72b3bb650ce1dd13fb7def6cf10656437dcf/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_i686.whl\", hash = \"sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922\", size = 745233, upload-time = \"2025-11-16T20:22:45.833Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/75/05/91130633602d6ba7ce3e07f8fc865b40d2a09efd4751c740df89eed5caf9/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_x86_64.whl\", hash = \"sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490\", size = 770963, upload-time = \"2025-11-16T16:13:19.344Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/fd/4b/fd4542e7f33d7d1bc64cc9ac9ba574ce8cf145569d21f5f20133336cdc8c/ruamel_yaml_clib-0.2.15-cp311-cp311-win32.whl\", hash = \"sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c\", size = 102640, upload-time = \"2025-11-16T16:13:20.498Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/bb/eb/00ff6032c19c7537371e3119287999570867a0eafb0154fccc80e74bf57a/ruamel_yaml_clib-0.2.15-cp311-cp311-win_amd64.whl\", hash = \"sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e\", size = 121996, upload-time = \"2025-11-16T16:13:21.855Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl\", hash = \"sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff\", size = 149088, upload-time = \"2025-11-16T16:13:22.836Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl\", hash = \"sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2\", size = 134553, upload-time = \"2025-11-16T16:13:24.151Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl\", hash = \"sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1\", size = 737468, upload-time = \"2025-11-16T20:22:47.335Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60\", size = 753349, upload-time = \"2025-11-16T16:13:26.269Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9\", size = 788211, upload-time = \"2025-11-16T16:13:27.441Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl\", hash = \"sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642\", size = 743203, upload-time = \"2025-11-16T16:13:28.671Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl\", hash = \"sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690\", size = 747292, upload-time = \"2025-11-16T20:22:48.584Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl\", hash = \"sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a\", size = 771624, upload-time = \"2025-11-16T16:13:29.853Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl\", hash = \"sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144\", size = 100342, upload-time = \"2025-11-16T16:13:31.067Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl\", hash = \"sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc\", size = 119013, upload-time = \"2025-11-16T16:13:32.164Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/17/5e/2f970ce4c573dc30c2f95825f2691c96d55560268ddc67603dc6ea2dd08e/ruamel_yaml_clib-0.2.15-cp313-cp313-macosx_10_13_x86_64.whl\", hash = \"sha256:4dcec721fddbb62e60c2801ba08c87010bd6b700054a09998c4d09c08147b8fb\", size = 147450, upload-time = \"2025-11-16T16:13:33.542Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/d6/03/a1baa5b94f71383913f21b96172fb3a2eb5576a4637729adbf7cd9f797f8/ruamel_yaml_clib-0.2.15-cp313-cp313-macosx_11_0_arm64.whl\", hash = \"sha256:65f48245279f9bb301d1276f9679b82e4c080a1ae25e679f682ac62446fac471\", size = 133139, upload-time = \"2025-11-16T16:13:34.587Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/dc/19/40d676802390f85784235a05788fd28940923382e3f8b943d25febbb98b7/ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl\", hash = \"sha256:46895c17ead5e22bea5e576f1db7e41cb273e8d062c04a6a49013d9f60996c25\", size = 731474, upload-time = \"2025-11-16T20:22:49.934Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/ce/bb/6ef5abfa43b48dd55c30d53e997f8f978722f02add61efba31380d73e42e/ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:3eb199178b08956e5be6288ee0b05b2fb0b5c1f309725ad25d9c6ea7e27f962a\", size = 748047, upload-time = \"2025-11-16T16:13:35.633Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/ff/5d/e4f84c9c448613e12bd62e90b23aa127ea4c46b697f3d760acc32cb94f25/ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:4d1032919280ebc04a80e4fb1e93f7a738129857eaec9448310e638c8bccefcf\", size = 782129, upload-time = \"2025-11-16T16:13:36.781Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/de/4b/e98086e88f76c00c88a6bcf15eae27a1454f661a9eb72b111e6bbb69024d/ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_aarch64.whl\", hash = \"sha256:ab0df0648d86a7ecbd9c632e8f8d6b21bb21b5fc9d9e095c796cacf32a728d2d\", size = 736848, upload-time = \"2025-11-16T16:13:37.952Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/0c/5c/5964fcd1fd9acc53b7a3a5d9a05ea4f95ead9495d980003a557deb9769c7/ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_i686.whl\", hash = \"sha256:331fb180858dd8534f0e61aa243b944f25e73a4dae9962bd44c46d1761126bbf\", size = 741630, upload-time = \"2025-11-16T20:22:51.718Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/07/1e/99660f5a30fceb58494598e7d15df883a07292346ef5696f0c0ae5dee8c6/ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_x86_64.whl\", hash = \"sha256:fd4c928ddf6bce586285daa6d90680b9c291cfd045fc40aad34e445d57b1bf51\", size = 766619, upload-time = \"2025-11-16T16:13:39.178Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/36/2f/fa0344a9327b58b54970e56a27b32416ffbcfe4dcc0700605516708579b2/ruamel_yaml_clib-0.2.15-cp313-cp313-win32.whl\", hash = \"sha256:bf0846d629e160223805db9fe8cc7aec16aaa11a07310c50c8c7164efa440aec\", size = 100171, upload-time = \"2025-11-16T16:13:40.456Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/06/c4/c124fbcef0684fcf3c9b72374c2a8c35c94464d8694c50f37eef27f5a145/ruamel_yaml_clib-0.2.15-cp313-cp313-win_amd64.whl\", hash = \"sha256:45702dfbea1420ba3450bb3dd9a80b33f0badd57539c6aac09f42584303e0db6\", size = 118845, upload-time = \"2025-11-16T16:13:41.481Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/3e/bd/ab8459c8bb759c14a146990bf07f632c1cbec0910d4853feeee4be2ab8bb/ruamel_yaml_clib-0.2.15-cp314-cp314-macosx_10_15_x86_64.whl\", hash = \"sha256:753faf20b3a5906faf1fc50e4ddb8c074cb9b251e00b14c18b28492f933ac8ef\", size = 147248, upload-time = \"2025-11-16T16:13:42.872Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/69/f2/c4cec0a30f1955510fde498aac451d2e52b24afdbcb00204d3a951b772c3/ruamel_yaml_clib-0.2.15-cp314-cp314-macosx_11_0_arm64.whl\", hash = \"sha256:480894aee0b29752560a9de46c0e5f84a82602f2bc5c6cde8db9a345319acfdf\", size = 133764, upload-time = \"2025-11-16T16:13:43.932Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/82/c7/2480d062281385a2ea4f7cc9476712446e0c548cd74090bff92b4b49e898/ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl\", hash = \"sha256:4d3b58ab2454b4747442ac76fab66739c72b1e2bb9bd173d7694b9f9dbc9c000\", size = 730537, upload-time = \"2025-11-16T20:22:52.918Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/75/08/e365ee305367559f57ba6179d836ecc3d31c7d3fdff2a40ebf6c32823a1f/ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl\", hash = \"sha256:bfd309b316228acecfa30670c3887dcedf9b7a44ea39e2101e75d2654522acd4\", size = 746944, upload-time = \"2025-11-16T16:13:45.338Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/a1/5c/8b56b08db91e569d0a4fbfa3e492ed2026081bdd7e892f63ba1c88a2f548/ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl\", hash = \"sha256:2812ff359ec1f30129b62372e5f22a52936fac13d5d21e70373dbca5d64bb97c\", size = 778249, upload-time = \"2025-11-16T16:13:46.871Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/6a/1d/70dbda370bd0e1a92942754c873bd28f513da6198127d1736fa98bb2a16f/ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_aarch64.whl\", hash = \"sha256:7e74ea87307303ba91073b63e67f2c667e93f05a8c63079ee5b7a5c8d0d7b043\", size = 737140, upload-time = \"2025-11-16T16:13:48.349Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/5b/87/822d95874216922e1120afb9d3fafa795a18fdd0c444f5c4c382f6dac761/ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_i686.whl\", hash = \"sha256:713cd68af9dfbe0bb588e144a61aad8dcc00ef92a82d2e87183ca662d242f524\", size = 741070, upload-time = \"2025-11-16T20:22:54.151Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/b9/17/4e01a602693b572149f92c983c1f25bd608df02c3f5cf50fd1f94e124a59/ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_x86_64.whl\", hash = \"sha256:542d77b72786a35563f97069b9379ce762944e67055bea293480f7734b2c7e5e\", size = 765882, upload-time = \"2025-11-16T16:13:49.526Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/9f/17/7999399081d39ebb79e807314de6b611e1d1374458924eb2a489c01fc5ad/ruamel_yaml_clib-0.2.15-cp314-cp314-win32.whl\", hash = \"sha256:424ead8cef3939d690c4b5c85ef5b52155a231ff8b252961b6516ed7cf05f6aa\", size = 102567, upload-time = \"2025-11-16T16:13:50.78Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/d2/67/be582a7370fdc9e6846c5be4888a530dcadd055eef5b932e0e85c33c7d73/ruamel_yaml_clib-0.2.15-cp314-cp314-win_amd64.whl\", hash = \"sha256:ac9b8d5fa4bb7fd2917ab5027f60d4234345fd366fe39aa711d5dca090aa1467\", size = 122847, upload-time = \"2025-11-16T16:13:51.807Z\" },\n+]\n+\n [[package]]\n name = \"ruff\"\n version = \"0.15.10\"\n"]}]},{"kind":"pr","number":32034,"title":"fix(utils): unify YAML list indent across all config writers (#31999)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"0aea0c36544479305c31813bd3db62b8a1c1374c","number":0},{"kind":"pr","value":"#52386","number":52386},{"kind":"pr","value":"#52386","number":52386},{"kind":"pr","value":"#32034","number":32034}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":50706,"title":"fix: preserve comments when using hermes config set","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"43504da0e5993750e1e5816f47365c43c29f79af","number":0},{"kind":"pr","value":"#118918","number":118918},{"kind":"pr","value":"#92554","number":92554},{"kind":"pr","value":"#63039","number":63039},{"kind":"pr","value":"#50698","number":50698}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":52386,"title":"fix(utils): unify YAML list indent across all config writers (#31999)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["plugins/platforms/telegram/adapter.py","tests/test_yaml_indent_consistency_31999.py","tui_gateway/server.py","utils.py"]},{"kind":"pr","number":63140,"title":"fix(cli): preserve YAML comments and formatting in set_config_value","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#50706","number":50706},{"kind":"pr","value":"#63039","number":63039},{"kind":"pr","value":"#50706","number":50706},{"kind":"pr","value":"#50706","number":50706},{"kind":"pr","value":"#50706","number":50706}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":63665,"title":"fix(config): preserve config set YAML comments","url":null,"state":"closed","close_reason":{"category":"volume_quota","implication":"active"},"merged":false},{"kind":"pr","number":72581,"title":"fix(config): preserve config.yaml comments/formatting in `hermes config set` + `unset` (#63039, salvage of #50706)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"43504da0e5993750e1e5816f47365c43c29f79af","number":0},{"kind":"pr","value":"#118918","number":118918},{"kind":"pr","value":"#92554","number":92554},{"kind":"pr","value":"#63039","number":63039},{"kind":"pr","value":"#50698","number":50698}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":107537,"title":"fix(config): preserve unrelated YAML list indent on config set","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"43504da0e5993750e1e5816f47365c43c29f79af","number":0},{"kind":"pr","value":"#118918","number":118918},{"kind":"pr","value":"#92554","number":92554},{"kind":"pr","value":"#63039","number":63039},{"kind":"pr","value":"#50698","number":50698}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":111106,"title":"fix(config): preserve comments and key order in config set/unset","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"43504da0e5993750e1e5816f47365c43c29f79af","number":0},{"kind":"pr","value":"#118918","number":118918},{"kind":"pr","value":"#92554","number":92554},{"kind":"pr","value":"#63039","number":63039},{"kind":"pr","value":"#50698","number":50698}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":118918,"title":"config.yaml comments survive every writer: one comment-preserving writer + CI guard (#92554, #63039, #50698, #107511)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":[".github/workflows/lint.yml","hermes_cli/AGENTS.md","hermes_cli/auth.py","hermes_cli/backup.py","hermes_cli/config.py","hermes_cli/credential_lifecycle.py","hermes_cli/profile_channels.py","hermes_cli/profiles.py","plugins/platforms/telegram/adapter.py","scripts/check_config_yaml_writers.py","tests/gateway/test_dm_topics.py","tests/hermes_cli/test_config.py","tests/hermes_cli/test_config_yaml_comment_preservation.py","tests/hermes_cli/test_model_provider_persistence.py","tui_gateway/server.py","utils.py"]}],"trust":{"default_tier":"verified","includes_suggested":false}}