fix(config): preserve platform_toolsets during v25→v26 config migration (#38798) - #39581
fix(config): preserve platform_toolsets during v25→v26 config migration (#38798)#39581ashishpatel26 wants to merge 2 commits into
Conversation
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved
Fixes a data-loss regression in the v25-to-v26 config migration that previously wiped user-custom platform_toolsets entries. The fix:
- Replaces the destructive empty-list seeding with an additive preserve-and-coerce strategy
- Never overwrites existing per-platform keys
- Coerces malformed non-list entries to [] instead of deleting
- Adds thorough regression tests covering the exact bug scenario
Looks Good
- Defender-style coding: coercion within a try/except block prevents migrator crashes from corrupt configs
- Regression tests explicitly name issue #38798 and pin the expected behavior
- Tests cover three edge cases: custom toolset lists, absent platform_toolsets, and malformed non-list entries
- Change is backwards-compatible with DEFAULT_CONFIG deep-merge still filling in new platforms
Suggestions (non-blocking)
- Consider logging a WARNING-level dedup message when coercion occurs, so power users can detect and clean broken toolset entries
Reviewed by Hermes Agent (cron)
|
Thanks for the thorough review and approval. Added the WARNING log in the latest push — when coercion occurs, it now emits: \ This gives power users a clear signal in logs that something was wrong with their config, without breaking the migration. |
…ion (NousResearch#38798) A previous draft of the v25->v26 migration block incorrectly seeded platform_toolsets with empty lists for platforms that appear in the new display.platforms streaming defaults (telegram, discord), silently wiping all configured tools for users who had custom toolset lists. The fix adds an explicit v25->v26 migration block that: - Reads the raw on-disk platform_toolsets (not the deep-merged value) so only user-written entries are touched. - Preserves every existing per-platform toolset list verbatim. - Only coerces non-list entries to [] (defensive, never drops real lists). - Never injects a default platform_toolsets when the key is absent; it is seeded lazily by tools_config.py on first use instead. - display.platforms streaming defaults (telegram on, discord off) are already filled by the DEFAULT_CONFIG deep-merge in load_config() and require no explicit migration write. Adds three regression tests that pin the invariant: custom toolsets on v25 survive migration intact, absent platform_toolsets stays absent, and a single-platform cli-only entry is preserved.
cc0e291 to
497db6b
Compare
…opping tools (#38798) A config migration (or hand-edit) that leaves an invalid toolset name in `platform_toolsets` — e.g. the #38798 corruption that rewrote `hermes-cli` to the non-existent `hermes` — silently disabled all affected tools: resolve_toolset() returns [] for an unknown name, so the agent quietly lost its tools with no error, warning, or log entry and degraded to text-only replies. Surface it loudly at two points: - After migration (migrate_config): validate platform_toolsets and record/print a warning per unknown name, with a `hermes-<platform>` suggestion when that would have been valid (the exact #38798 shape). - At runtime (_get_platform_tools): if a platform was explicitly configured but every toolset name is invalid, log a warning when tools are resolved for a session — so an ALREADY-corrupted config is caught at startup, not only on the next `hermes update`. Logic lives in a new pure, side-effect-free helper (toolset_validation.py) with validate_toolset injected, so it is unit-testable without the tool registry. Note: the original v25→v26 migration that caused the corruption no longer exists (config format is now v30; no migration step rewrites toolset names). This change is the durable defense against the silent-failure mode regardless of cause, matching the issue's "Expected: log a warning". Salvaged from #39207 by @lEWFkRAD (authorship preserved via cherry-pick). Tests: 9 helper cases (incl. the #38798 corruption shape, mixed valid/invalid, zero-tools state, non-dict/scalar/non-string) + a runtime caplog test — both the helper warning and the runtime guard mutation-verified to fail without the fix. Closes #38798. Supersedes #39581 (prevent-in-v25→v26 — that path is gone), #41006 / #40208 (repair-migration for already-corrupted configs).
|
Thanks @ashishpatel26 — closing in favor of #52920 (#38798). Your approach (preserve |
…opping tools (NousResearch#38798) A config migration (or hand-edit) that leaves an invalid toolset name in `platform_toolsets` — e.g. the NousResearch#38798 corruption that rewrote `hermes-cli` to the non-existent `hermes` — silently disabled all affected tools: resolve_toolset() returns [] for an unknown name, so the agent quietly lost its tools with no error, warning, or log entry and degraded to text-only replies. Surface it loudly at two points: - After migration (migrate_config): validate platform_toolsets and record/print a warning per unknown name, with a `hermes-<platform>` suggestion when that would have been valid (the exact NousResearch#38798 shape). - At runtime (_get_platform_tools): if a platform was explicitly configured but every toolset name is invalid, log a warning when tools are resolved for a session — so an ALREADY-corrupted config is caught at startup, not only on the next `hermes update`. Logic lives in a new pure, side-effect-free helper (toolset_validation.py) with validate_toolset injected, so it is unit-testable without the tool registry. Note: the original v25→v26 migration that caused the corruption no longer exists (config format is now v30; no migration step rewrites toolset names). This change is the durable defense against the silent-failure mode regardless of cause, matching the issue's "Expected: log a warning". Salvaged from NousResearch#39207 by @lEWFkRAD (authorship preserved via cherry-pick). Tests: 9 helper cases (incl. the NousResearch#38798 corruption shape, mixed valid/invalid, zero-tools state, non-dict/scalar/non-string) + a runtime caplog test — both the helper warning and the runtime guard mutation-verified to fail without the fix. Closes NousResearch#38798. Supersedes NousResearch#39581 (prevent-in-v25→v26 — that path is gone), NousResearch#41006 / NousResearch#40208 (repair-migration for already-corrupted configs).
…opping tools (NousResearch#38798) A config migration (or hand-edit) that leaves an invalid toolset name in `platform_toolsets` — e.g. the NousResearch#38798 corruption that rewrote `hermes-cli` to the non-existent `hermes` — silently disabled all affected tools: resolve_toolset() returns [] for an unknown name, so the agent quietly lost its tools with no error, warning, or log entry and degraded to text-only replies. Surface it loudly at two points: - After migration (migrate_config): validate platform_toolsets and record/print a warning per unknown name, with a `hermes-<platform>` suggestion when that would have been valid (the exact NousResearch#38798 shape). - At runtime (_get_platform_tools): if a platform was explicitly configured but every toolset name is invalid, log a warning when tools are resolved for a session — so an ALREADY-corrupted config is caught at startup, not only on the next `hermes update`. Logic lives in a new pure, side-effect-free helper (toolset_validation.py) with validate_toolset injected, so it is unit-testable without the tool registry. Note: the original v25→v26 migration that caused the corruption no longer exists (config format is now v30; no migration step rewrites toolset names). This change is the durable defense against the silent-failure mode regardless of cause, matching the issue's "Expected: log a warning". Salvaged from NousResearch#39207 by @lEWFkRAD (authorship preserved via cherry-pick). Tests: 9 helper cases (incl. the NousResearch#38798 corruption shape, mixed valid/invalid, zero-tools state, non-dict/scalar/non-string) + a runtime caplog test — both the helper warning and the runtime guard mutation-verified to fail without the fix. Closes NousResearch#38798. Supersedes NousResearch#39581 (prevent-in-v25→v26 — that path is gone), NousResearch#41006 / NousResearch#40208 (repair-migration for already-corrupted configs).
…opping tools (NousResearch#38798) A config migration (or hand-edit) that leaves an invalid toolset name in `platform_toolsets` — e.g. the NousResearch#38798 corruption that rewrote `hermes-cli` to the non-existent `hermes` — silently disabled all affected tools: resolve_toolset() returns [] for an unknown name, so the agent quietly lost its tools with no error, warning, or log entry and degraded to text-only replies. Surface it loudly at two points: - After migration (migrate_config): validate platform_toolsets and record/print a warning per unknown name, with a `hermes-<platform>` suggestion when that would have been valid (the exact NousResearch#38798 shape). - At runtime (_get_platform_tools): if a platform was explicitly configured but every toolset name is invalid, log a warning when tools are resolved for a session — so an ALREADY-corrupted config is caught at startup, not only on the next `hermes update`. Logic lives in a new pure, side-effect-free helper (toolset_validation.py) with validate_toolset injected, so it is unit-testable without the tool registry. Note: the original v25→v26 migration that caused the corruption no longer exists (config format is now v30; no migration step rewrites toolset names). This change is the durable defense against the silent-failure mode regardless of cause, matching the issue's "Expected: log a warning". Salvaged from NousResearch#39207 by @lEWFkRAD (authorship preserved via cherry-pick). Tests: 9 helper cases (incl. the NousResearch#38798 corruption shape, mixed valid/invalid, zero-tools state, non-dict/scalar/non-string) + a runtime caplog test — both the helper warning and the runtime guard mutation-verified to fail without the fix. Closes NousResearch#38798. Supersedes NousResearch#39581 (prevent-in-v25→v26 — that path is gone), NousResearch#41006 / NousResearch#40208 (repair-migration for already-corrupted configs).
…opping tools (NousResearch#38798) A config migration (or hand-edit) that leaves an invalid toolset name in `platform_toolsets` — e.g. the NousResearch#38798 corruption that rewrote `hermes-cli` to the non-existent `hermes` — silently disabled all affected tools: resolve_toolset() returns [] for an unknown name, so the agent quietly lost its tools with no error, warning, or log entry and degraded to text-only replies. Surface it loudly at two points: - After migration (migrate_config): validate platform_toolsets and record/print a warning per unknown name, with a `hermes-<platform>` suggestion when that would have been valid (the exact NousResearch#38798 shape). - At runtime (_get_platform_tools): if a platform was explicitly configured but every toolset name is invalid, log a warning when tools are resolved for a session — so an ALREADY-corrupted config is caught at startup, not only on the next `hermes update`. Logic lives in a new pure, side-effect-free helper (toolset_validation.py) with validate_toolset injected, so it is unit-testable without the tool registry. Note: the original v25→v26 migration that caused the corruption no longer exists (config format is now v30; no migration step rewrites toolset names). This change is the durable defense against the silent-failure mode regardless of cause, matching the issue's "Expected: log a warning". Salvaged from NousResearch#39207 by @lEWFkRAD (authorship preserved via cherry-pick). Tests: 9 helper cases (incl. the NousResearch#38798 corruption shape, mixed valid/invalid, zero-tools state, non-dict/scalar/non-string) + a runtime caplog test — both the helper warning and the runtime guard mutation-verified to fail without the fix. Closes NousResearch#38798. Supersedes NousResearch#39581 (prevent-in-v25→v26 — that path is gone), NousResearch#41006 / NousResearch#40208 (repair-migration for already-corrupted configs).
…opping tools (NousResearch#38798) A config migration (or hand-edit) that leaves an invalid toolset name in `platform_toolsets` — e.g. the NousResearch#38798 corruption that rewrote `hermes-cli` to the non-existent `hermes` — silently disabled all affected tools: resolve_toolset() returns [] for an unknown name, so the agent quietly lost its tools with no error, warning, or log entry and degraded to text-only replies. Surface it loudly at two points: - After migration (migrate_config): validate platform_toolsets and record/print a warning per unknown name, with a `hermes-<platform>` suggestion when that would have been valid (the exact NousResearch#38798 shape). - At runtime (_get_platform_tools): if a platform was explicitly configured but every toolset name is invalid, log a warning when tools are resolved for a session — so an ALREADY-corrupted config is caught at startup, not only on the next `hermes update`. Logic lives in a new pure, side-effect-free helper (toolset_validation.py) with validate_toolset injected, so it is unit-testable without the tool registry. Note: the original v25→v26 migration that caused the corruption no longer exists (config format is now v30; no migration step rewrites toolset names). This change is the durable defense against the silent-failure mode regardless of cause, matching the issue's "Expected: log a warning". Salvaged from NousResearch#39207 by @lEWFkRAD (authorship preserved via cherry-pick). Tests: 9 helper cases (incl. the NousResearch#38798 corruption shape, mixed valid/invalid, zero-tools state, non-dict/scalar/non-string) + a runtime caplog test — both the helper warning and the runtime guard mutation-verified to fail without the fix. Closes NousResearch#38798. Supersedes NousResearch#39581 (prevent-in-v25→v26 — that path is gone), NousResearch#41006 / NousResearch#40208 (repair-migration for already-corrupted configs).
…opping tools (NousResearch#38798) A config migration (or hand-edit) that leaves an invalid toolset name in `platform_toolsets` — e.g. the NousResearch#38798 corruption that rewrote `hermes-cli` to the non-existent `hermes` — silently disabled all affected tools: resolve_toolset() returns [] for an unknown name, so the agent quietly lost its tools with no error, warning, or log entry and degraded to text-only replies. Surface it loudly at two points: - After migration (migrate_config): validate platform_toolsets and record/print a warning per unknown name, with a `hermes-<platform>` suggestion when that would have been valid (the exact NousResearch#38798 shape). - At runtime (_get_platform_tools): if a platform was explicitly configured but every toolset name is invalid, log a warning when tools are resolved for a session — so an ALREADY-corrupted config is caught at startup, not only on the next `hermes update`. Logic lives in a new pure, side-effect-free helper (toolset_validation.py) with validate_toolset injected, so it is unit-testable without the tool registry. Note: the original v25→v26 migration that caused the corruption no longer exists (config format is now v30; no migration step rewrites toolset names). This change is the durable defense against the silent-failure mode regardless of cause, matching the issue's "Expected: log a warning". Salvaged from NousResearch#39207 by @lEWFkRAD (authorship preserved via cherry-pick). Tests: 9 helper cases (incl. the NousResearch#38798 corruption shape, mixed valid/invalid, zero-tools state, non-dict/scalar/non-string) + a runtime caplog test — both the helper warning and the runtime guard mutation-verified to fail without the fix. Closes NousResearch#38798. Supersedes NousResearch#39581 (prevent-in-v25→v26 — that path is gone), NousResearch#41006 / NousResearch#40208 (repair-migration for already-corrupted configs).
Summary
Root cause (fixes #38798)
The v25→v26 version bump added no migration block. Any migration code that naively set platform_toolsets to a default value would wipe user-configured toolsets, silently disabling all their tools. The explicit preservation block with tests prevents this.
🤖 Generated with Claude Code