hermes - ✅(Solved) Fix Dashboard PUT /api/config destroys unrelated config sections (no merge, whole-file replace) [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#13396Fetched 2026-04-22 08:06:45
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×2

PR fix notes

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

Description (problem / solution / changelog)

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

Changed files

  • agent/anthropic_adapter.py (modified, +7/-0)
  • agent/auxiliary_client.py (modified, +10/-2)
  • cli.py (modified, +15/-1)
  • hermes_cli/web_server.py (modified, +5/-1)
  • scripts/release.py (modified, +2/-0)

PR #106: fix(config): deep-merge dashboard saveConfig to prevent data loss (#85)

Description (problem / solution / changelog)

Fixes #85 — a critical silent data-loss bug in zero-fork mode.

Root cause

saveConfig() in src/server/hermes-dashboard-api.ts was sending a PUT /api/config with only the partial fields the caller passed. The Hermes dashboard's handler replaces the entire config on write, so tweaking display.personality in Settings wiped model.provider, custom_providers, compression, and auxiliary.* from ~/.hermes/config.yaml.

Fix

Client-side fetch-then-deep-merge before the PUT:

  1. GET /api/config
  2. deep-merge the caller's patch on top (arrays + primitives replace; objects recurse; null = explicit removal)
  3. PUT the merged config

Callers still pass ONLY the fields they want to change — no API change.

Why this path (not upstream Hermes)

@joaompfp's writeup linked NousResearch/hermes-agent#13396 for a server-side merge. That's the right long-term fix but depends on upstream. This PR ships the immediate user-facing fix in hermes-workspace without waiting. Once the upstream merge lands, this client-side merge is redundant but harmless (deep-merge of full config against itself is idempotent).

Test plan

  • pnpm tsc --noEmit passes
  • With dashboard running + multi-section config.yaml: tweak one field in Settings → other sections survive
  • Null in a patch removes that key
  • Array values are replaced wholesale (not concatenated)

Impact

Severe — resolves silent data loss. Users on zero-fork mode with provider/compression/auxiliary configs were losing them on any Settings change.

Changed files

  • src/server/hermes-dashboard-api.ts (modified, +64/-1)
RAW_BUFFERClick to expand / collapse

Problem

hermes_cli/web_server.pyupdate_config() calls save_config(_denormalize_config_from_web(body.config)), and hermes_cli/config.pysave_config() replaces the entire ~/.hermes/config.yaml file with the provided dict.

If a caller (e.g. a dashboard UI) sends a PUT payload containing only the sections it manages (agent, display), the write silently deletes everything else — model, custom_providers, compression, auxiliary, fallback_model, etc.

Reproduction

  1. Configure Hermes with: model, custom_providers, compression (etc.) in config.yaml.

  2. From a UI client or curl:

    curl -X PUT http://127.0.0.1:9119/api/config \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"config": {"agent": {"max_turns": 90}, "display": {"personality": "concise"}}}'

  3. Inspect ~/.hermes/config.yaml → all other sections are gone.

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.

Expected behavior (proposals, any of the below)

(a) Deep-merge on server side. In update_config():

existing = load_config()
merged = _deep_merge(existing, _denormalize_config_from_web(body.config))
save_config(merged)

(b) Require the client to send the full config and reject PUTs that omit top-level keys present on disk (409 Conflict).

(c) Make save_config() take an explicit scope parameter listing which top-level keys to overwrite. Anything outside that scope is preserved.

I'd vote for (a) — it's the least surprising behavior and matches what every third-party client is already assuming.

Env

  • Hermes v0.10.0
  • Triggered by: outsourc-e/hermes-workspace Settings screen on x.joao.date
  • Lost fields included: model, custom_providers, compression, auxiliary.vision

Happy to open a PR for option (a).

extent analysis

TL;DR

To prevent silent deletion of configuration sections, implement a deep-merge strategy in the update_config() function to combine the existing configuration with the updated sections.

Guidance

  • Identify the update_config() function in hermes_cli/web_server.py and modify it to load the existing configuration using load_config().
  • Implement a deep-merge function (_deep_merge()) to combine the existing configuration with the updated sections from the PUT request.
  • Update the save_config() function to accept the merged configuration.
  • Test the changes with a sample PUT request to ensure that only the intended sections are updated.

Example

def update_config(body):
    existing = load_config()
    merged = _deep_merge(existing, _denormalize_config_from_web(body.config))
    save_config(merged)

Notes

The proposed solution assumes that the _deep_merge() function is implemented correctly to handle nested dictionaries. Additionally, this solution may require modifications to the load_config() and save_config() functions to ensure compatibility with the updated update_config() function.

Recommendation

Apply workaround (a) by implementing a deep-merge strategy in the update_config() function, as it is the least surprising behavior and matches the assumptions of third-party clients.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Dashboard PUT /api/config destroys unrelated config sections (no merge, whole-file replace) [2 pull requests, 1 participants]