fix(config): route every migration write through one default-stripping chokepoint - #55715
Merged
kshitijk4poor merged 1 commit intoJun 30, 2026
Conversation
…g chokepoint
A single 'hermes update' / 'hermes -p' could rewrite a hand-curated config.yaml
into a near-full DEFAULT_CONFIG dump (the 'you blow up my profile config on one
tweak' reports). Root cause: migrate_config() had ~16 independent save_config()
call sites, each author deciding ad hoc whether to materialise a value, and many
persisted pure schema defaults with strip_defaults=False. Defaults already merge
transparently at read time via load_config(), so writing them is pure bloat that
also shadows future default changes (see save_config's docstring).
Architectural fix (not a per-site patch): introduce a single _persist_migration()
chokepoint that enforces one invariant — a migration may persist only values that
DIFFER from the current schema default, plus explicit removals/renames of user
data; pure defaults are never written. Every migration write (all 17 sites incl.
the version-bump finalizer) now routes through it. The invariant is mechanically
correct for all cases and verified empirically:
- pure-default seeds (timezone='', curator/auxiliary.curator blocks, interim
flag, curator.consolidate=False, empty plugins.enabled) are stripped → merged
in at read time;
- non-default values (write_approval=True, model_catalog.ttl_hours=1) preserved
via explicit-raw-path preservation;
- behaviour flips (agent.verify_on_stop=False, schema default still 'auto')
preserved because False != 'auto';
- data transforms (custom_providers->providers, stt.model relocation,
write_mode->write_approval, compression.summary_* removal, MCP-disable)
persist their removals/renames.
An explicitly user-set non-default value (e.g. matrix.require_mention: false) is
preserved across the bump.
Guard tests lock the architecture: an AST check asserts migrate_config() makes no
direct save_config() call (all writes go through _persist_migration), and a
full-range v1->latest test asserts a lean config is never dumped. Two existing
change-detector tests that froze the on-disk representation of default-valued
keys are rewritten to assert the effective value via load_config() (behaviour
contract, not snapshot).
Validation: lean v1->latest migration drops from ~567 bytes to ~196 bytes;
148 config+setup and 196 profile/curator/migrate tests pass on scripts/run_tests.sh.
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
…igration-no-default-expansion fix(config): route every migration write through one default-stripping chokepoint
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
…igration-no-default-expansion fix(config): route every migration write through one default-stripping chokepoint
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
…igration-no-default-expansion fix(config): route every migration write through one default-stripping chokepoint
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…igration-no-default-expansion fix(config): route every migration write through one default-stripping chokepoint
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
…igration-no-default-expansion fix(config): route every migration write through one default-stripping chokepoint
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
hermes update/hermes -pno longer rewrites a hand-curatedconfig.yamlinto a near-fullDEFAULT_CONFIGdump on a version bump. All config migration writes now flow through a single default-stripping chokepoint, so only values that differ from the schema default (plus explicit user-data removals/renames) ever land on disk — defaults merge transparently at read time viaload_config().Root cause:
migrate_config()had ~16 independentsave_config()call sites. Each migration author decided ad hoc whether to materialise a value, and many persisted pure schema defaults withstrip_defaults=False, bypassing the default-stripping protection added in #27539/#53132. Because there was no single rule, every prior fix patched individual sites and the bug-class kept returning. Writing a default to disk is not just bloat — it shadows future default changes (the on-disk value wins the merge forever).Changes
hermes_cli/config.py:_persist_migration(config)chokepoint — a thin wrapper oversave_config(config)(default-stripping ON) documenting the migration write invariant.strip_defaults=Falseis gone from the migration path.get_missing_config_fields()finalizer no longer injects every missing default to disk — it only surfaces the list for the informational "N new config option(s) available" display and persists the version bump.tests/hermes_cli/test_config.py:TestMigrationWriteInvariant— AST guard assertingmigrate_config()makes no directsave_config()call (regression-proof), plus a full-range v1→latest leanness test.write_approval,interim_assistant_messages) rewritten to assert the effective value viaload_config()(behavior contract, not snapshot).The invariant (enforced in one place)
A migration may persist only values that differ from the current schema default, plus explicit removals/renames of user data. Verified empirically for every category:
save_config's explicit-raw-path preservation;"auto") → preserved becauseFalse != "auto";An explicitly user-set non-default value (e.g.
matrix.require_mention: false) is preserved across the bump.Validation
scripts/run_tests.sh tests/hermes_cli/test_config.py tests/hermes_cli/test_setup.py→ 148 passed. Migration-adjacent suites (profiles, curator, migrate_xai, apply_profile_override) → 196 passed. ruff clean.Relates to the config-bloat reports addressed piecemeal in #27354 / #40821 / #27539 / #53132; this makes the fix structural so the bug-class can't recur.