Files
hermes-agent/website/docs/reference
teknium1 9a2d96ca11 fix(config): flag quoted list/mapping values in doctor + startup, guard the list slots config set missed
A list/mapping slot holding ONE quoted string (`plugins:\n  enabled: '["a","b"]'`,
`model_catalog:\n  excluded_providers: '["openai-api"]'`) is skipped by every
isinstance-gated reader (`plugins_cmd._config_name_set` -> set(), `plugins._names`,
`inventory.excluded_providers` -> []) while `config get` echoes it back, so user
plugins silently unmount and provider exclusions silently lapse. Neither `hermes
doctor` nor the startup `print_config_warnings` banner said a word.

Reader/doctor half: one schema-aware pass in `validate_config_structure`
(`_validate_quoted_containers`) walks `DEFAULT_CONFIG` (sections included) plus
`_KNOWN_CONTAINER_TYPES` and warns when the user value is a string that parses to
a list/mapping. The message names the key, the quoted value and the remedy
(`hermes config set <key> '<literal>'`). Finding only: the file is never
rewritten. String-typed keys (`approvals.mode: "[off]"`), the `model: <name>`
shorthand and the `parse_config_string_list`-read slots
(`agent.disabled_toolsets`, `skills.disabled`) are not flagged. Feeds both the
doctor "Config Structure" section and the startup banner.

Writer half (audit of every path that can put a string in a container slot):
- `hermes config set` (`hermes_cli/config.py::set_config_value`): already parses
  bracket/brace literals (#88163) and refuses wrong-shaped values
  (`_refuse_container_type_mismatch`), BUT the guard only knew slots present in
  DEFAULT_CONFIG or `_KNOWN_CONTAINER_TYPES`. `plugins.enabled`,
  `plugins.disabled` and `model_catalog.excluded_providers` are deliberately
  absent from DEFAULT_CONFIG, so `config set plugins.enabled foo` /
  `plugins.enabled a,b` / `model_catalog.excluded_providers openai-api` stored a
  plain string (live repro on base). Added the three keys to
  `_KNOWN_CONTAINER_TYPES`: those writes are now refused with the literal hint.
- `cli.py::save_config_value` + callers (cli_*_mixin, gateway/slash_commands,
  gateway/run_busy): every caller passes a bool/enum string for scalar keys; no
  list-slot caller. Not reachable.
- `hermes_cli/plugins_cmd.py::_save_plugin_sets` / `_write_config_value` and
  `plugins_cmd_catalog` (via `_save_enabled_set`): write `sorted(set)` — real
  lists. Not reachable.
- Dashboard `PUT /api/config` (`web_routers/config_env.py::update_config` ->
  `_denormalize_config_from_web` -> `save_config`): schema-driven form;
  `web/src/components/AutoField.tsx` splits list-typed fields into a real array
  before the PUT. Not reachable.
- tui_gateway `config.set`: fixed `_CONFIG_SETTERS` table of scalar keys only
  (out of this lane's files anyway). Not reachable.
Conclusion: the quoted shapes on real machines are leftovers of pre-#88163
`config set` runs plus the DEFAULT_CONFIG-absent slots fixed here.

Live repro (fake HOME/HERMES_HOME, both quoted shapes in config.yaml):
  before: validate_config_structure() -> []; startup banner silent; doctor has
          no Config Structure section; _config_name_set('plugins','enabled') -> set()
  after:  two warnings naming plugins.enabled / model_catalog.excluded_providers
          with `hermes config set ... '["a","b"]'`; doctor prints them under
          Config Structure; running the remedy yields real lists,
          validate -> [], _config_name_set -> {'a','b'}
  writer: `config set plugins.enabled a,b` wrote `enabled: a,b` on base; now
          refused ("must be a list ... nothing was written").

Fixes #83308
Fixes #105706
credit: @fangliquanflq #105725 (schema-aware detection in validate_config_structure; slim redo)
credit: @Luna161 #83313 (first report + per-key warning in plugins_cmd)
2026-09-21 23:31:02 -07:00
..