Skip to content

fix(web): deep-merge config on PUT /api/config to preserve unrelated sections#13914

Open
HiddenPuppy wants to merge 7 commits into
NousResearch:mainfrom
HiddenPuppy:fix/config-put-merge
Open

fix(web): deep-merge config on PUT /api/config to preserve unrelated sections#13914
HiddenPuppy wants to merge 7 commits into
NousResearch:mainfrom
HiddenPuppy:fix/config-put-merge

Conversation

@HiddenPuppy

Copy link
Copy Markdown
Contributor

Summary

Fixes #13396

Problem

PUT /api/config replaces the entire ~/.hermes/config.yaml file with the provided payload. If a dashboard UI sends only the sections it manages (e.g. agent, display), everything else (model, custom_providers, compression, auxiliary, fallback_model, etc.) is silently deleted.

Impact

  • Third-party dashboards (e.g. outsourc-e/hermes-workspace Settings screen) silently destroy user-managed fields they don't know about
  • Users have to re-edit config.yaml from memory
  • Happens invisibly — no warning, no backup before the overwrite

Fix

In update_config(), load the existing config first, deep-merge the incoming payload into it, then save the merged result. Keys present in the payload overwrite existing values; keys absent from the payload are preserved.

The _deep_merge function already exists in hermes_cli/config.py and is used elsewhere for the same purpose.

Changes

  • hermes_cli/web_server.py: update_config() — added load_config() + _deep_merge() before save_config()

Verification

  • curl -X PUT /api/config with partial payload → inspect config.yaml → unrelated sections preserved
  • Keys in payload correctly overwrite existing values

Checklist

  • Bug fix (non-breaking change which fixes an issue)
  • New feature
  • Breaking change
  • Requires documentation update

Jerome added 6 commits April 22, 2026 15:18
…call messages for Kimi /coding

Fixes NousResearch#13848

Kimi's /coding endpoint speaks the Anthropic Messages protocol but has its
own thinking semantics: when thinking is enabled, Kimi validates message
history and requires every prior assistant tool-call message to carry
OpenAI-style reasoning_content.

The Anthropic path never populated that field, and
convert_messages_to_anthropic strips all Anthropic thinking blocks on
third-party endpoints — so the request failed with HTTP 400:
  "thinking is enabled but reasoning_content is missing in assistant
tool call message at index N"

Now, when an assistant message contains tool_calls and a
reasoning_content string, we append a {"type": "thinking", ...} block
to the Anthropic content so Kimi can validate the history.  This only
affects assistant messages with tool_calls + reasoning_content; plain
text assistant messages are unchanged.
Map tsuijinglei@gmail.com → hiddenpuppy.
…t exhaustion for fallback

Fixes NousResearch#13887

OpenRouter returns HTTP 403 (not 402) for key-limit and spend-limit
errors.  The existing _is_payment_error() only checked 402/429/None,
so these 403s were not classified as payment exhaustion and auxiliary
calls would fail immediately instead of falling back to the next provider.

Now _is_payment_error() also checks status == 403 and looks for
OpenRouter-specific keywords: key limit, spending limit, spend limit,
total limit, credit limit, quota exceeded.

This keeps the fix scoped: only 403s with these specific phrases are
treated as payment errors.  Other 403s (auth, forbidden, etc.) still
raise normally.

The fallback path already skips the failed provider via
_try_payment_fallback(), so no changes are needed there.
Map jerome@clawwork.ai → HiddenPuppy.
…H/tunnels

Fixes NousResearch#13870

prompt_toolkit sends \x1b[6n (Device Status Report) to query cursor
position; the terminal replies with \x1b[row;colR. Over SSH/cloudflared
tunnels these CPR responses leak as raw text (e.g. 20
…sections

Fixes NousResearch#13396

PUT /api/config was calling save_config() with only the payload from the
client, which replaced the entire ~/.hermes/config.yaml file. If a dashboard
UI sent only the sections it manages (agent, display), everything else
(model, custom_providers, compression, auxiliary, fallback_model, etc.) was
silently deleted.

Now update_config() loads the existing config, deep-merges the incoming
payload into it, and saves the merged result. Keys present in the payload
overwrite existing values; keys absent from the payload are preserved.

This matches the behavior third-party clients already assume and prevents
accidental data loss.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles labels Apr 22, 2026
@austinpickett austinpickett requested a review from Copilot May 18, 2026 14:53

@austinpickett austinpickett left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use PULL_REQUEST_TEMPLATE.md, fix merge conflicts.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

The PR's stated purpose is to fix issue #13396 by deep-merging the incoming payload of PUT /api/config into the on-disk config so unrelated sections aren't silently destroyed. However, the diff also bundles four unrelated changes (mailmap additions, a prompt_toolkit CPR workaround, OpenRouter 403 payment-error detection, and Kimi reasoning_content preservation in the Anthropic adapter) that aren't mentioned in the description.

Changes:

  • update_config() now loads the existing config, deep-merges the incoming payload, and saves the merged result.
  • Several unrelated fixes are bundled in (release mailmap, TUI CPR disable, payment-error and Anthropic-thinking-block tweaks).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
hermes_cli/web_server.py Imports _deep_merge and merges incoming PUT payload onto load_config() before saving.
scripts/release.py Adds two .mailmap-style author entries.
cli.py Constructs a Vt100_Output with enable_cpr=False to suppress CPR escape leaks over SSH/cloudflared.
agent/auxiliary_client.py Treats certain OpenRouter HTTP 403 messages (key/spend/credit limit) as payment-exhaustion errors.
agent/anthropic_adapter.py Emits a thinking block carrying assistant reasoning_content for Kimi /coding tool-call messages.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hermes_cli/web_server.py Outdated
Comment on lines +945 to +948
existing = load_config()
incoming = _denormalize_config_from_web(body.config)
merged = _deep_merge(existing, incoming)
save_config(merged)
Comment thread hermes_cli/web_server.py Outdated
remove_env_value,
check_config_version,
redact_key,
_deep_merge,
Comment thread hermes_cli/web_server.py Outdated
Comment on lines +945 to +948
existing = load_config()
incoming = _denormalize_config_from_web(body.config)
merged = _deep_merge(existing, incoming)
save_config(merged)
…/api/config

Addresses Copilot review comments:
1. Use read_raw_config() instead of load_config() as the merge base —
   load_config() injects DEFAULT_CONFIG values and expands  env refs,
   which would bloat the saved file with defaults and leak expanded secrets.
   read_raw_config() returns the exact user-authored YAML without defaults
   or env expansion, which is the correct base for a merge-and-write.
2. Add public merge_and_save_config() in config.py so web_server.py doesn't
   import the private _deep_merge helper across modules.
3. update_config() now calls merge_and_save_config(incoming) for a single
   clear read-merge-save operation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard PUT /api/config destroys unrelated config sections (no merge, whole-file replace)

4 participants