fix(reconciler): prefer config.json over state manager for enabled mismatch (#353)

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sarjent
2026-05-26 17:55:30 -05:00
committed by GitHub
parent 7603909c59
commit 35c540d0e0

View File

@@ -347,8 +347,8 @@ class StateReconciliation:
inconsistency_type=InconsistencyType.PLUGIN_ENABLED_MISMATCH, inconsistency_type=InconsistencyType.PLUGIN_ENABLED_MISMATCH,
description=f"Plugin {plugin_id} enabled state mismatch: config={config_enabled}, state_manager={state_mgr_enabled}", description=f"Plugin {plugin_id} enabled state mismatch: config={config_enabled}, state_manager={state_mgr_enabled}",
fix_action=FixAction.AUTO_FIX, fix_action=FixAction.AUTO_FIX,
current_state={'enabled': config_enabled}, current_state={'enabled': state_mgr_enabled},
expected_state={'enabled': state_mgr_enabled}, expected_state={'enabled': config_enabled},
can_auto_fix=True can_auto_fix=True
)) ))
@@ -371,15 +371,23 @@ class StateReconciliation:
return self._auto_repair_missing_plugin(inconsistency.plugin_id) return self._auto_repair_missing_plugin(inconsistency.plugin_id)
elif inconsistency.inconsistency_type == InconsistencyType.PLUGIN_ENABLED_MISMATCH: elif inconsistency.inconsistency_type == InconsistencyType.PLUGIN_ENABLED_MISMATCH:
# Sync enabled state from state manager to config # config.json is the user-editable source of truth for enabled state.
expected_enabled = inconsistency.expected_state.get('enabled') # Bring the state manager in sync with config rather than the reverse,
config = self.config_manager.load_config() # so that manual config edits (or the state left behind after an
if inconsistency.plugin_id not in config: # uninstall+reinstall cycle) don't silently override the user's intent.
config[inconsistency.plugin_id] = {} config_enabled = inconsistency.expected_state.get('enabled')
config[inconsistency.plugin_id]['enabled'] = expected_enabled success = self.state_manager.set_plugin_enabled(inconsistency.plugin_id, config_enabled)
self.config_manager.save_config(config) if success:
self.logger.info(f"Fixed: Synced enabled state for {inconsistency.plugin_id}") self.logger.info(
return True 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: except Exception as e:
self.logger.error(f"Error fixing inconsistency: {e}", exc_info=True) self.logger.error(f"Error fixing inconsistency: {e}", exc_info=True)