From 35c540d0e0a5fc533ffda626f06aa758b9d64936 Mon Sep 17 00:00:00 2001 From: sarjent <35471573+sarjent@users.noreply.github.com> Date: Tue, 26 May 2026 17:55:30 -0500 Subject: [PATCH] fix(reconciler): prefer config.json over state manager for enabled mismatch (#353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(reconciler): prefer config.json over state manager for enabled mismatch When the enabled state in config.json and plugin_state.json diverged, the reconciler was syncing config.json to match plugin_state.json (state manager wins). This silently disabled plugins on every restart whenever the state file had an outdated enabled=false entry — most commonly after an uninstall+reinstall cycle, where the reinstall left the plugin in the installed-but-not-enabled state while config.json still had enabled=true. Flip the sync direction: update plugin_state.json via set_plugin_enabled() to match config.json instead. config.json is the user-editable source of truth; the state file is an internal tracker that should follow it when they disagree. Co-Authored-By: Claude Sonnet 4.6 * fix(reconciler): return set_plugin_enabled result instead of always True Capture the boolean returned by set_plugin_enabled() and propagate it so reconciliation accurately reflects failure to update the state manager. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- src/plugin_system/state_reconciliation.py | 32 ++++++++++++++--------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/plugin_system/state_reconciliation.py b/src/plugin_system/state_reconciliation.py index 70b32458..8cc70e4d 100644 --- a/src/plugin_system/state_reconciliation.py +++ b/src/plugin_system/state_reconciliation.py @@ -340,15 +340,15 @@ class StateReconciliation: # Check: Enabled state mismatch config_enabled = config.get('enabled', False) state_mgr_enabled = state_mgr.get('enabled') - + if state_mgr_enabled is not None and config_enabled != state_mgr_enabled: inconsistencies.append(Inconsistency( plugin_id=plugin_id, inconsistency_type=InconsistencyType.PLUGIN_ENABLED_MISMATCH, description=f"Plugin {plugin_id} enabled state mismatch: config={config_enabled}, state_manager={state_mgr_enabled}", fix_action=FixAction.AUTO_FIX, - current_state={'enabled': config_enabled}, - expected_state={'enabled': state_mgr_enabled}, + current_state={'enabled': state_mgr_enabled}, + expected_state={'enabled': config_enabled}, can_auto_fix=True )) @@ -371,15 +371,23 @@ class StateReconciliation: return self._auto_repair_missing_plugin(inconsistency.plugin_id) elif inconsistency.inconsistency_type == InconsistencyType.PLUGIN_ENABLED_MISMATCH: - # Sync enabled state from state manager to config - expected_enabled = inconsistency.expected_state.get('enabled') - config = self.config_manager.load_config() - if inconsistency.plugin_id not in config: - config[inconsistency.plugin_id] = {} - config[inconsistency.plugin_id]['enabled'] = expected_enabled - self.config_manager.save_config(config) - self.logger.info(f"Fixed: Synced enabled state for {inconsistency.plugin_id}") - return True + # config.json is the user-editable source of truth for enabled state. + # Bring the state manager in sync with config rather than the reverse, + # so that manual config edits (or the state left behind after an + # uninstall+reinstall cycle) don't silently override the user's intent. + config_enabled = inconsistency.expected_state.get('enabled') + success = self.state_manager.set_plugin_enabled(inconsistency.plugin_id, config_enabled) + if success: + self.logger.info( + f"Fixed: Synced state manager enabled={config_enabled} for " + f"{inconsistency.plugin_id} to match config" + ) + else: + self.logger.warning( + f"Failed to sync state manager enabled={config_enabled} for " + f"{inconsistency.plugin_id}" + ) + return success except Exception as e: self.logger.error(f"Error fixing inconsistency: {e}", exc_info=True)