hermes-agent/hermes_cli/subcommands
Bartok9 70679ada61 fix(config): validate config-key schema, refuse unknown keys (#34067)
Fixes #34067. 'hermes config set <unknown.key.path> <value>' silently
accepted arbitrary key paths, wrote them to config.yaml, and reported
success — but the runtime/gateway never read them.

The headline case from the issue: a user typing
  hermes config set gateway.discord.gateway_restart_notification false
gets success, but the value lands at config.yaml:gateway.discord.* where
nothing reads it. The correct path is discord.gateway_restart_notification
(platform configs live at the top level of DEFAULT_CONFIG, not under
a 'platforms' namespace). The user reasonably believes the change took
effect, then loses time debugging behavior that hasn't changed.

Fix: schema-validate the dotted key path against DEFAULT_CONFIG before
writing. Walk DEFAULT_CONFIG along the user's segments and:

  - Reject unknown top-level keys with a fuzzy-match suggestion
  - Reject unknown sub-keys by suggesting the closest sibling
  - Accept anything below open-dict shapes (mcp_servers.<name>.command,
    providers.<openrouter>.api_key, etc.)
  - Accept anything below schema-defined-extensible shapes (platform
    configs like discord.*, telegram.* — PlatformConfig has dynamic
    'extra' fields, so deep validation is unsafe)
  - Special-case 'platforms.X' → suggest 'X' (the actual top-level layout)

Bypass with --force for forward-compatibility with keys a newer Hermes
version adds but the running version doesn't recognize yet:
  hermes config set --force brand_new_future_key value

API-key style names (OPENROUTER_API_KEY, *_TOKEN, etc.) still route to
.env before schema validation runs, so this is non-breaking for that path.

Adds 21 regression tests across TestSchemaValidation + TestValidateConfigKey
covering: unknown top-level keys, unknown sub-keys (the headline bug),
platforms.* prefix suggestions, fuzzy-match top-level typos, sibling-
suggestion sub-key typos, --force bypass, and that known config keys
(simple, platform-extensible, open-dict) still work.

Also updates 2 pre-existing tests that used non-canonical paths
(platforms.telegram.* and 'verbose') which schema validation correctly
flags — switched to canonical paths (telegram.* and agent.gateway_timeout).

All 53 tests in test_set_config_value.py pass.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-20 05:38:59 -07:00
..
__init__.py refactor(cli): extract hermes cron parser into hermes_cli/subcommands/ (god-file Phase 2) 2026-06-07 22:18:14 -07:00
_shared.py refactor(cli): extract hermes cron parser into hermes_cli/subcommands/ (god-file Phase 2) 2026-06-07 22:18:14 -07:00
acp.py refactor(cli): promote 9 closure handlers to top-level + extract their parsers (god-file Phase 2 follow-up) 2026-06-07 22:56:23 -07:00
auth.py feat(auth): make xAI Grok OAuth device-code-only, drop loopback login 2026-07-02 13:17:41 -07:00
backup.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
claw.py refactor(cli): promote 9 closure handlers to top-level + extract their parsers (god-file Phase 2 follow-up) 2026-06-07 22:56:23 -07:00
config.py fix(config): validate config-key schema, refuse unknown keys (#34067) 2026-07-20 05:38:59 -07:00
console.py Add safe Hermes console REPL 2026-07-03 20:18:00 +05:30
cron.py fix(cron): harden execution attempt ledger 2026-07-17 04:58:35 -07:00
dashboard.py feat(cli): make hermes serve a real headless backend 2026-06-30 17:58:52 -05:00
debug.py fix(security): require explicit consent before uploading debug logs 2026-07-01 00:38:17 -07:00
doctor.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
dump.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
gateway.py fix(gateway): preserve external supervisor ownership 2026-07-16 05:08:56 -07:00
gui.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
hooks.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
import_cmd.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
insights.py refactor(cli): promote 9 closure handlers to top-level + extract their parsers (god-file Phase 2 follow-up) 2026-06-07 22:56:23 -07:00
login.py fix(cli): deprecated hermes login fails gracefully for any provider 2026-06-17 12:55:40 +05:30
logout.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
logs.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
mcp.py feat(cli): add --connect-timeout flag to hermes mcp add 2026-07-05 19:10:00 -07:00
memory.py refactor(cli): promote 9 closure handlers to top-level + extract their parsers (god-file Phase 2 follow-up) 2026-06-07 22:56:23 -07:00
model.py feat(auth): make xAI Grok OAuth device-code-only, drop loopback login 2026-07-02 13:17:41 -07:00
pairing.py refactor(cli): promote 9 closure handlers to top-level + extract their parsers (god-file Phase 2 follow-up) 2026-06-07 22:56:23 -07:00
plugins.py feat(plugins): enable-time consent prompt for tool_override grant 2026-06-30 04:00:42 -07:00
postinstall.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
profile.py fix(profile): make clone-from a full source selector 2026-06-13 07:33:58 -07:00
prompt_size.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
security.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
setup.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
skills.py fix(skills-hub): surface per-tap providers (NVIDIA/OpenAI/...) in runtime search (#53191) 2026-06-26 11:04:41 -07:00
slack.py feat(slack): support agent view manifests 2026-07-14 13:58:36 -07:00
status.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
tools.py refactor(cli): promote 9 closure handlers to top-level + extract their parsers (god-file Phase 2 follow-up) 2026-06-07 22:56:23 -07:00
uninstall.py feat: add uninstall dry-run mode 2026-07-07 05:12:24 -07:00
update.py fix(update): consolidate pre-update backups into one gated mechanism (#65754) 2026-07-16 08:47:25 -07:00
version.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00
webhook.py feat(gateway): add webhook payload filters 2026-07-08 08:10:55 -07:00
whatsapp.py refactor(cli): extract 25 more subcommand parsers into hermes_cli/subcommands/ 2026-06-07 22:18:14 -07:00