hermes-agent/hermes_cli
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
..
dashboard_auth
…
proxy
…
subcommands fix(config): validate config-key schema, refuse unknown keys (#34067) 2026-07-20 05:38:59 -07:00
__init__.py
…
_parser.py
…
_subprocess_compat.py
…
active_sessions.py
…
auth.py
…
auth_commands.py
…
azure_detect.py
…
backup.py fix(discord): harden reconnect message recovery 2026-07-18 14:01:33 -07:00
banner.py
…
blueprint_cmd.py
…
browser_connect.py fix: /browser connect times out when another app squats the CDP port 2026-07-18 02:49:28 -07:00
build_info.py
…
bundles.py
…
callbacks.py
…
checkpoints.py
…
claw.py
…
cli_agent_setup_mixin.py
…
cli_billing_mixin.py feat(tui+cli): change your Nous plan from the terminal (/subscription, /topup, terminal-billing UX) (#51639) 2026-07-18 14:30:24 +05:30
cli_commands_mixin.py fix: session-scoped /fast + full /new reset to config defaults (#67979) 2026-07-20 03:27:22 -07:00
cli_output.py
…
clipboard.py
…
codex_models.py
…
codex_runtime_plugin_migration.py
…
codex_runtime_switch.py
…
colors.py
…
commands.py fix: session-scoped /fast + full /new reset to config defaults (#67979) 2026-07-20 03:27:22 -07:00
completion.py
…
config.py fix(config): validate config-key schema, refuse unknown keys (#34067) 2026-07-20 05:38:59 -07:00
console_engine.py
…
container_boot.py
…
context_switch_guard.py
…
copilot_auth.py
…
credential_lifecycle.py fix(credentials): unified provider key delete/update across .env, auth.json, config.yaml (#67213) 2026-07-19 03:02:21 -07:00
cron.py
…
curator.py
…
curses_ui.py
…
dashboard_register.py
…
debug.py
…
default_soul.py
…
dep_ensure.py
…
diagnostics_upload.py
…
dingtalk_auth.py
…
doctor.py feat(providers): add ``enabled: false`` flag to hide a provider 2026-07-20 03:06:02 -07:00
dump.py fix(cron): accept UTF-8 BOM when reading jobs.json 2026-07-18 02:31:20 -07:00
env_loader.py fix(cli): warn once per path for UTF-32 .env refuse-to-mangle 2026-07-18 19:01:10 -04:00
fallback_cmd.py
…
fallback_config.py
…
gateway.py fix(gateway): recover Discord websocket and event-loop stalls 2026-07-18 20:01:55 +05:30
gateway_enroll.py
…
gateway_windows.py
…
goals.py fix(goals): auto-pause goal loop on consecutive transport failures 2026-07-20 05:38:25 -07:00
gui_uninstall.py
…
hooks.py
…
input_sanitize.py
…
inventory.py feat(model-switch): excluded_providers config to hide providers from /model picker 2026-07-20 03:06:02 -07:00
journey.py
…
kanban.py fix: migrate CLI kanban gate + remaining mocks to 5-value judge contract 2026-07-20 05:38:25 -07:00
kanban_db.py
…
kanban_decompose.py
…
kanban_diagnostics.py
…
kanban_specify.py
…
kanban_swarm.py
…
logs.py
…
main.py feat(model-switch): excluded_providers config to hide providers from /model picker 2026-07-20 03:06:02 -07:00
managed_scope.py
…
managed_uv.py
…
mcp_catalog.py
…
mcp_config.py
…
mcp_picker.py
…
mcp_security.py
…
mcp_startup.py
…
memory_oauth.py
…
memory_setup.py
…
middleware.py
…
migrate.py
…
moa_cmd.py
…
moa_config.py
…
model_catalog.py
…
model_cost_guard.py
…
model_normalize.py
…
model_setup_flows.py
…
model_switch.py feat(providers): post-filter picker by ``enabled: false`` for built-ins 2026-07-20 03:06:02 -07:00
models.py fix: dedupe claude-sonnet-5 entries after overlapping salvages 2026-07-20 02:25:44 -07:00
nous_account.py
…
nous_auth_keepalive.py
…
nous_billing.py Terminal-billing client hardening: shared wire types, wire-layer tests, dead RPC removal (#61067) 2026-07-18 07:05:58 -07:00
nous_subscription.py
…
onepassword_secrets_cli.py
…
oneshot.py refactor(cli): apply /simplify-code findings to oneshot teardown 2026-07-20 12:59:28 +05:30
pairing.py
…
partial_compress.py
…
pets.py
…
platforms.py
…
plugins.py
…
plugins_cmd.py
…
portal_cli.py
…
profile_describer.py
…
profile_distribution.py
…
profiles.py
…
projects_cmd.py
…
projects_db.py
…
prompt_size.py
…
provider_catalog.py
…
providers.py
…
psutil_android.py
…
pt_input_extras.py
…
pty_bridge.py
…
pty_session.py
…
relaunch.py
…
runtime_provider.py feat(providers): extend ``enabled: false`` gate to built-in resolution 2026-07-20 03:06:02 -07:00
secret_prompt.py
…
secrets_cli.py
…
security_advisories.py
…
security_audit.py
…
security_audit_startup.py
…
send_cmd.py
…
service_manager.py
…
session_export.py
…
session_export_html.py
…
session_export_md.py
…
session_filters.py
…
session_listing.py
…
session_recap.py
…
setup.py feat(models): add Claude Sonnet 5 support 2026-07-20 02:25:44 -07:00
setup_whatsapp_cloud.py
…
skills_config.py
…
skills_hub.py
…
skin_engine.py
…
slack_cli.py
…
sqlite_util.py
…
status.py fix(cron): accept UTF-8 BOM when reading jobs.json 2026-07-18 02:31:20 -07:00
stdio.py
…
suggestions_cmd.py
…
telegram_managed_bot.py
…
timeouts.py
…
tips.py
…
tools_config.py feat(desktop): five Capabilities-tab UX fixes from live testing — hints, vision link, web split, key deep-links (#67482) 2026-07-19 05:45:01 -07:00
toolset_validation.py
…
uninstall.py
…
urllib_security.py
…
voice.py
…
web_git.py
…
web_server.py feat(desktop): inline TTS voice/model settings in the Capabilities tab (#68017) 2026-07-20 05:38:38 -07:00
webhook.py
…
win_pty_bridge.py
…
write_approval_commands.py
…
xai_retirement.py
…