fix(config): decode JSON array literals into YAML lists on config set - #76470
fix(config): decode JSON array literals into YAML lists on config set#76470thatssoheil wants to merge 3 commits into
Conversation
pestoura
left a comment
There was a problem hiding this comment.
The implementation does not currently enforce the scope described in the PR. The new array branch sits inside if not isinstance(_default_value_for_key(key), str), so it runs not only for list-typed settings, but also for bool/int/float defaults and for unknown keys (_default_value_for_key() returns None). Unknown keys are intentionally supported as arbitrary string configuration for skills and external apps, so hermes config set custom.payload '["a"]' now changes from a string to a YAML sequence despite the stated compatibility boundary.
Please gate JSON-array decoding on the schema default actually being a list, e.g. cache the default once and use isinstance(default_value, list). Add regressions for an unknown key and a non-list known key receiving a valid JSON array, confirming both remain strings, alongside the existing list-key case.
e298cff to
b9b2b74
Compare
|
Thanks for the sharp catch — you are right that the first version decoded JSON arrays for unknown keys (default Refined gate in
Unknown keys ( New regressions (in
Verified: 89/89 in |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for narrowing the JSON decoding gate after the earlier review feedback. The current-main premise is real: hermes_cli/config.py:4869-4878 leaves JSON arrays as scalar strings, while Telegram’s list parser only recognizes actual lists or comma-separated scalars (plugins/platforms/telegram/adapter.py:7820-7827).
Problems
hermes_cli/config.py:4938only recognizes list defaults, existing lists, and paths containingplatforms. The documented top-level configuration form istelegram.group_allowed_chats(website/docs/user-guide/messaging/telegram.md:138-145); it has no list default inhermes_cli/config_defaults.py:1936-1944and noplatformssegment, so this PR still serializes its JSON array as a scalar._under_platform_container()athermes_cli/config.py:4655also matchesgateway.platforms.telegram, which is the platform mapping itself rather than a field under it.
Suggested changes
- Cover direct top-level platform fields alongside nested platform fields, and add a regression that loads the result through
load_gateway_config(). - Require a field below the platform name before treating a
platformspath as array-decodable.
Automated hermes-sweeper review.
| JSON array literal for these is a list write, not a string. | ||
| """ | ||
| segments = dotted_key.split(".") | ||
| return any(seg == "platforms" and idx + 1 < len(segments) for idx, seg in enumerate(segments)) |
There was a problem hiding this comment.
This also returns true for gateway.platforms.telegram (and platforms.telegram), where the path names the platform mapping rather than a field below it. Require a field segment after the platform name before enabling JSON-array decoding.
| # ``platforms.<name>.<field>`` — open-dict extras with no | ||
| # DEFAULT_CONFIG entry, e.g. ``group_allowed_chats``). Unknown keys | ||
| # never match and keep their historical stringification. | ||
| elif isinstance(default_value, list) or isinstance(existing_value, list) or _under_platform_container(key): |
There was a problem hiding this comment.
This gate misses the documented direct form telegram.group_allowed_chats: it has no list default and no platforms segment, so a fresh JSON-array write remains a scalar. Please cover direct platform config fields and add a gateway-loading regression.
b9b2b74 to
9a6abd6
Compare
|
Both points addressed in the current head ( 1. Top-level platform fields now decode. 2. New regressions:
Verified: 92/92 in |
9a6abd6 to
d463090
Compare
|
Addressed the code-review findings in the current head (d463090b5): Standards axis:
Spec axis:
Verified: 93/93 |
SummaryOne PR addresses #76457. #76470 targets the reported scalar-string serialization failure by decoding JSON arrays for schema-list, existing-list, and allowlist-shaped platform fields, with regressions for YAML persistence and gateway loading, but its added mapping-key branch exceeds that list-focused scope. Related pull requests
Suggested consolidationKeep #76470 open with a salvage path, consistent with the contributor keep_open review: retain the list-typed and allowlist-field decoding plus the gateway-loading regressions, but restrict the mapping-key branch to decoded dictionaries and add a regression proving that JSON arrays on mapping-only keys remain strings. There are no duplicate PRs to close. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I76457(["issue #76457 (open)"])
P76470["PR #76470 (open)"]
P76470 -->|best fix| I76457
class I76457 open
class P76470 open
class P76470 best
class P76470 target
click I76457 "https://github.com/NousResearch/hermes-agent/issues/76457"
click P76470 "https://github.com/NousResearch/hermes-agent/pull/76470"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 13 kB of PR diffs, 7 kB of issue/PR text, 8 kB of discussion (9 comments), 2 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
hermes config set <allowlist-key> '["-5488240624"]' previously wrote the literal as a quoted scalar string. Readers that split scalars on commas (_coerce_allow_set, the telegram env bridge) then matched nothing, silently breaking authorization allowlists like gateway.platforms.telegram.group_allowed_chats. When the schema default is a list and the passed value parses as a JSON array, decode it into a real YAML sequence. Scalars, comma strings, and JSON objects keep their historical behavior. Closes NousResearch#76457
d463090 to
1aa616f
Compare
GottZ triage (PR NousResearch#76470) correctly identified that the mapping-key branch accepted any structured _decode_json_value result, so a JSON array on a mapping-only key (e.g. terminal) would become a YAML list instead of retaining historical string semantics. Now checks isinstance(decoded, dict) before applying the mapping decode, so JSON arrays on mapping-typed keys stay strings. Added regression test test_json_array_on_mapping_key_stays_a_string.
|
Addressed the mapping-key over-decode concern from the GottZ triage in 47659a2: Restricted mapping branch to dicts only: The mapping-key decode path now checks Regression test: New 94/94 tests pass. |
|
@pestoura Good catch on the scope concern — I traced through the logic and both cases you flagged are already handled correctly in the current code (47659a2):
Both cases have regression tests that pass:
The GottZ triage fix (restricting the mapping branch to dicts) was the missing piece — without it, a JSON array on a mapping-typed key like |
|
@teknium1 Both concerns are already addressed in the current code (47659a2):
The GottZ triage fix (this PR's scope) was specifically about restricting the mapping-key branch to dicts only. The allowlist field detection was already correct before this PR. |
|
@teknium1 @pestoura All review findings are addressed in the current head (47659a2) and CI is green (45 checks, 0 failures):
94/94 tests in |
|
Resolved via PR #88163 (merged) — |
Summary
hermes config set <allowlist-key> '["-5488240624"]'wrote the JSON literal as a quoted scalar string into config.yaml. Readers that split scalars on commas (_coerce_allow_set, the telegram env bridge) then matched nothing — silently breaking authorization allowlists likegateway.platforms.telegram.group_allowed_chats.Fixes #76457.
Before
After
Approach (write side, per the issue's preferred direction)
In
set_config_value(), when the schema default is a list and the passed value parses as a JSON array, decode it into a real YAML sequence. All other values keep historical behavior:_coerce_allow_setstill splits them)Tests
Added
TestJsonListValuesintests/hermes_cli/test_set_config_value.py(5 cases): array→list, scalar stays string, comma-string stays string, object stays string, string-typed default keeps literal.scripts/run_tests.sh tests/hermes_cli/test_set_config_value.py→ 87 passed. Also rantest_config.py,test_managed_scope_writeguard.py,test_telegram_auth_check.py,test_telegram_group_gating.py→ all green.