openclaw - 💡(How to fix) Fix [P0] Config change detection bug: Any modification triggers gateway restart [1 comments, 2 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
openclaw/openclaw#72637Fetched 2026-04-28 06:33:49
View on GitHub
Comments
1
Participants
2
Timeline
2
Reactions
0
Timeline (top)
closed ×1commented ×1

Root Cause

🔍 Root Cause Analysis

Fix Action

Fix / Workaround

🛠️ Current Workarounds

  • Confirm bug reproduction (test in different environments)
  • Locate config change detection logic in codebase
  • Fix comparison algorithm to use deep equality
  • Ensure only truly changed paths are reported
  • Add unit tests covering this scenario
  • Release hotfix version

🐛 Additional Test: Added config: {} Workaround (Failed)

Code Example

openclaw gateway status
# Expected: Runtime: running

---

# Edit openclaw.json, enable or disable a skill
# Example: set xurl from false to true
vim ~/.openclaw/openclaw.json

---

tail -f /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log

---

config change detected; evaluating reload (skills.entries.xurl.enabled)
config change hot-reloaded (skills.entries.xurl.enabled)

---

[13:41:32] config change detected; evaluating reload (plugins.entries.ollama.config, plugins.entries.stepfun.config)
[13:41:33] config change requires gateway restart (plugins.entries.ollama.config, plugins.entries.stepfun.config)
[13:41:33] received SIGUSR1; restarting
[13:41:33] restart mode: full process restart

---

--- a/openclaw.json.bak
+++ b/openclaw.json
@@ -237,6 +237,9 @@
       },
       "clawhub": {
         "enabled": false
+      },
+      "wacli": {
+        "enabled": false
       }
     }
   }

---

[2026-04-27T13:37:57] config change detected; evaluating reload (
  agents.defaults.maxConcurrent,
  agents.defaults.subagents,
  models.providers.stepfun-plan.models,
  plugins.entries.ollama.config,NOT actually modified!
  plugins.entries.stepfun.config,NOT actually modified!
  skills.entries.wacli,
  commands,
  messages
)
[2026-04-27T13:37:57] config change requires gateway restart (plugins.entries.ollama.config, plugins.entries.stepfun.config)

---

"plugins": {
  "entries": {
    "ollama": {
      "enabled": true
      // Note: NO "config" sub-field
    },
    "stepfun": {
      "enabled": true
      // Note: NO "config" sub-field
    },
    "memory-core": {
      "config": {
        "dreaming": { "enabled": true }
      }
      // Has "config" sub-field
    },
    "bonjour": {
      "enabled": false
      // No "config" sub-field
    }
  }
}

---

// Broken config change detection
function detectConfigChanges(oldConfig, newConfig) {
  const changedPaths = [];

  // 1. Check if plugins object is "different" (reference or shallow compare)
  if (oldConfig.plugins !== newConfig.plugins) {
    // BUG: Incorrectly adds paths for all plugins with config
    // Even if config didn't change, just plugins object reference differs
    changedPaths.push('plugins.entries.ollama.config');
    changedPaths.push('plugins.entries.stepfun.config');
  }

  // 2. Check skills
  if (oldConfig.skills !== newConfig.skills) {
    changedPaths.push('skills.entries.' + changedSkill);
    // But doesn't clear previously added plugins paths!
  }

  return changedPaths;
}

---

const defaultPluginConfigPaths = [
    'plugins.entries.ollama.config',
    'plugins.entries.stepfun.config'
  ];
  if (pluginsChanged) {
    changedPaths.push(...defaultPluginConfigPaths);
  }

---

{
     "changedPaths": ["skills.entries.wacli.enabled"]
   }

---

// Use lodash.isEqual or JSON serialization for comparison
const isPluginsChanged = !isEqual(oldConfig.plugins, newConfig.plugins);

if (isPluginsChanged) {
  // Precisely identify which plugin configs actually changed
  const changedPluginPaths = findChangedPaths(
    oldConfig.plugins.entries,
    newConfig.plugins.entries
  );
  changedPaths.push(...changedPluginPaths);
}

---

// Register listeners for each config path
configWatcher.watch('skills.entries.*.enabled', () => {
  // Mark as hot-reload
});

configWatcher.watch('plugins.entries.*.config', () => {
  // Mark as restart-required
});

---

/tmp/openclaw/openclaw-2026-04-27.log

---

~/.openclaw/openclaw.json.bak
~/.openclaw/openclaw.json.before-test-20260427-134700
~/.openclaw/config-backups/

---

# 1. View current config
openclaw config get

# 2. Modify a skill (test)
python3 -c "
import json, time
with open('/home/jamesoldman/.openclaw/openclaw.json') as f:
    c = json.load(f)
c['skills']['entries']['xurl']['enabled'] = not c['skills']['entries']['xurl']['enabled']
with open('/home/jamesoldman/.openclaw/openclaw.json', 'w') as f:
    json.dump(c, f, indent=2)
print('xurl toggled')
"

# 3. Immediately check logs (watch for restart)
tail -n 50 /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log | grep -E "config change|restart"

# 4. Verify actual config unchanged
diff -u ~/.openclaw/openclaw.json.bak ~/.openclaw/openclaw.json | grep plugins || echo "plugins unchanged"
RAW_BUFFERClick to expand / collapse

[P0] Config change detection bug: Any modification triggers gateway restart

Version: v2026.4.24
Environment: WSL 2 (Ubuntu 22.04), Node v24.15.0
Severity: P0 - Critical (all config changes force restart)
Labels: bug, priority:critical, area:config, area:gateway


📋 Summary

Any configuration file modification (including only enabling/disabling a skill) causes the gateway log to incorrectly report that plugins.entries.ollama.config and plugins.entries.stepfun.config have changed, triggering a forced restart.

The actual configuration diff proves these two plugin configurations have not changed at all, but the config change detection logic erroneously marks them as "modified", causing unnecessary restarts.


🔁 Steps to Reproduce

Step 1: Prepare environment

openclaw gateway status
# Expected: Runtime: running

Step 2: Modify any skill configuration

# Edit openclaw.json, enable or disable a skill
# Example: set xurl from false to true
vim ~/.openclaw/openclaw.json

Step 3: Check logs

tail -f /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log

Expected (hot-reload):

config change detected; evaluating reload (skills.entries.xurl.enabled)
config change hot-reloaded (skills.entries.xurl.enabled)

Actual (forced restart):

[13:41:32] config change detected; evaluating reload (plugins.entries.ollama.config, plugins.entries.stepfun.config)
[13:41:33] config change requires gateway restart (plugins.entries.ollama.config, plugins.entries.stepfun.config)
[13:41:33] received SIGUSR1; restarting
[13:41:33] restart mode: full process restart

📊 Evidence

Test Case: Disable skill wacli

Time: 2026-04-27 13:37:57
Action: Click to disable wacli skill in Control UI

Configuration diff (actual changes):

--- a/openclaw.json.bak
+++ b/openclaw.json
@@ -237,6 +237,9 @@
       },
       "clawhub": {
         "enabled": false
+      },
+      "wacli": {
+        "enabled": false
       }
     }
   }

Only skills.entries.wacli.enabled changed, plugins configuration untouched!

Gateway log (detected "changes"):

[2026-04-27T13:37:57] config change detected; evaluating reload (
  agents.defaults.maxConcurrent,
  agents.defaults.subagents,
  models.providers.stepfun-plan.models,
  plugins.entries.ollama.config,      ← NOT actually modified!
  plugins.entries.stepfun.config,     ← NOT actually modified!
  skills.entries.wacli,
  commands,
  messages
)
[2026-04-27T13:37:57] config change requires gateway restart (plugins.entries.ollama.config, plugins.entries.stepfun.config)

Contradiction:

  • ✅ Actual config: plugins.entries.ollama = {"enabled": true} (unchanged)
  • ✅ Actual config: plugins.entries.stepfun = {"enabled": true} (unchanged)
  • ❌ Log claims: plugins.entries.ollama.config and plugins.entries.stepfun.config changed

Result: Gateway forced restart (SIGUSR1), service interrupted.


🎯 Impact

Affected operations (all trigger unwanted restart)

OperationExpectedActual
Disable/enable any skillHot-reload (no restart)🔴 Forced restart
Modify agents.defaults fieldsHot-reload or restart (depending)🔴 Forced restart
Add/remove agent (agents.add)Hot-reload🔴 Forced restart
Modify model configHot-reload🔴 Forced restart

Not affected

  • ✅ Pure agents.list reads (no change)
  • ✅ Gateway startup (initial load)

🔍 Root Cause Analysis

Current plugin configuration

"plugins": {
  "entries": {
    "ollama": {
      "enabled": true
      // Note: NO "config" sub-field
    },
    "stepfun": {
      "enabled": true
      // Note: NO "config" sub-field
    },
    "memory-core": {
      "config": {
        "dreaming": { "enabled": true }
      }
      // Has "config" sub-field
    },
    "bonjour": {
      "enabled": false
      // No "config" sub-field
    }
  }
}

Key observation:

  • ollama and stepfun do NOT have a config sub-object
  • But change detection logs always report plugins.entries.ollama.config and plugins.entries.stepfun.config

Suspected bug logic

OpenClaw internally likely has code similar to this (pseudocode):

// Broken config change detection
function detectConfigChanges(oldConfig, newConfig) {
  const changedPaths = [];

  // 1. Check if plugins object is "different" (reference or shallow compare)
  if (oldConfig.plugins !== newConfig.plugins) {
    // BUG: Incorrectly adds paths for all plugins with config
    // Even if config didn't change, just plugins object reference differs
    changedPaths.push('plugins.entries.ollama.config');
    changedPaths.push('plugins.entries.stepfun.config');
  }

  // 2. Check skills
  if (oldConfig.skills !== newConfig.skills) {
    changedPaths.push('skills.entries.' + changedSkill);
    // But doesn't clear previously added plugins paths!
  }

  return changedPaths;
}

Problems:

  1. Insufficient comparison depth: oldConfig.plugins !== newConfig.plugins uses object reference comparison; each file read creates new object, always returns true.
  2. Hardcoded or derived paths: Code may have hardcoded ollama and stepfun config paths, or incorrectly derives them.
  3. Accumulation: changedPaths array may not properly deduplicate or clear old values, causing irrelevant paths to be included.

Why ollama and stepfun specifically?

Looking at how these plugins are used:

  • They are core plugins enabled by default
  • They lack a config sub-field in configuration (only enabled)
  • Code may contain logic like:
    const defaultPluginConfigPaths = [
      'plugins.entries.ollama.config',
      'plugins.entries.stepfun.config'
    ];
    if (pluginsChanged) {
      changedPaths.push(...defaultPluginConfigPaths);
    }

🛠️ Current Workarounds

Due to this bug causing every config change to restart, current workflow adjustments:

Strategy 1: Batch changes, single restart

  • Combine all configuration changes into one edit
  • Accept one restart to complete all modifications
  • Avoid multiple restarts

Strategy 2: Config locking

  • Pin critical configs like agents.defaults
  • Avoid unnecessary modifications
  • Find alternative ways to enable/disable skills (TBD)

Strategy 3: Non-detected modification method (TBD)

  • Can API modifications avoid this detection?
  • Does API use the same detection logic?

✅ Expected Behavior

Correct config change detection should:

  1. Precise path tracking: Report only actually changed config paths

    {
      "changedPaths": ["skills.entries.wacli.enabled"]
    }
  2. Correct restart determination:

    • skills.entries.*.enabled → Hot-reload ✅
    • plugins.entries.*.enabled → Hot-reload ✅
    • plugins.entries.*.config → Restart ✅ (only when config actually modified)
    • agents.defaults.* → Restart ✅ (only when restart-required fields modified)
  3. Deep comparison: Use deep equality (e.g., lodash.isEqual or JSON serialization) to determine if objects truly changed, not reference comparison.

  4. Transparent logging: Log actual changed paths in the gateway log, not guessed or hardcoded paths.


💡 Suggested Fixes

Fix 1: Use deep equality

// Use lodash.isEqual or JSON serialization for comparison
const isPluginsChanged = !isEqual(oldConfig.plugins, newConfig.plugins);

if (isPluginsChanged) {
  // Precisely identify which plugin configs actually changed
  const changedPluginPaths = findChangedPaths(
    oldConfig.plugins.entries,
    newConfig.plugins.entries
  );
  changedPaths.push(...changedPluginPaths);
}

Fix 2: Path subscription mechanism

Event-driven path watching:

// Register listeners for each config path
configWatcher.watch('skills.entries.*.enabled', () => {
  // Mark as hot-reload
});

configWatcher.watch('plugins.entries.*.config', () => {
  // Mark as restart-required
});

Fix 3: Distinguish "structural" vs "value" changes

  • plugins.entries.ollama.enabled change → Hot-reload
  • plugins.entries.ollama.config deep value change → Restart
  • plugins.entries.ollama object reference change but content same → Ignore

📎 Additional Information

Relevant log excerpts

Full log saved at:

/tmp/openclaw/openclaw-2026-04-27.log

Key timestamps:

  • 2026-04-27T13:37:57 - wacli skill disable triggers restart
  • 2026-04-27T13:41:32 - xurl skill enable triggers restart (test)
  • 2026-04-27T12:05:41 - earlier same issue (apple-reminders)

Config backups

Related backup files:

~/.openclaw/openclaw.json.bak
~/.openclaw/openclaw.json.before-test-20260427-134700
~/.openclaw/config-backups/

🔗 Related Issues

  • Likely affects all config hot-reload functionality
  • Control UI config save affected
  • openclaw agents add and similar commands may trigger unexpected restarts

📌 Maintainer TODOs

  • Confirm bug reproduction (test in different environments)
  • Locate config change detection logic in codebase
  • Fix comparison algorithm to use deep equality
  • Ensure only truly changed paths are reported
  • Add unit tests covering this scenario
  • Release hotfix version

Reporter: butler (family butler)
Contact: OpenClaw Discord community
Last updated: 2026-04-27 13:59 GMT+8


⚡ Quick Verification Commands

# 1. View current config
openclaw config get

# 2. Modify a skill (test)
python3 -c "
import json, time
with open('/home/jamesoldman/.openclaw/openclaw.json') as f:
    c = json.load(f)
c['skills']['entries']['xurl']['enabled'] = not c['skills']['entries']['xurl']['enabled']
with open('/home/jamesoldman/.openclaw/openclaw.json', 'w') as f:
    json.dump(c, f, indent=2)
print('xurl toggled')
"

# 3. Immediately check logs (watch for restart)
tail -n 50 /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log | grep -E "config change|restart"

# 4. Verify actual config unchanged
diff -u ~/.openclaw/openclaw.json.bak ~/.openclaw/openclaw.json | grep plugins || echo "plugins unchanged"

🐛 Additional Test: Added config: {} Workaround (Failed)

Test: Added explicit empty config: {} to ollama and stepfun plugins to see if bug is related to missing config field.

Result: Bug persists. Restart still triggered.

Conclusion: Issue is not about missing config field; it's a fundamental flaw in change detection logic (likely object reference comparison instead of deep equality).

extent analysis

TL;DR

The most likely fix for the config change detection bug is to update the change detection logic to use deep equality comparison instead of object reference comparison.

Guidance

  • Review the detectConfigChanges function to identify the incorrect comparison logic and update it to use a deep equality check, such as lodash.isEqual or JSON serialization.
  • Verify that the changedPaths array only includes actually changed config paths and not hardcoded or derived paths.
  • Test the updated logic with various config changes to ensure correct behavior, including hot-reload and restart scenarios.
  • Consider implementing a path subscription mechanism or distinguishing between "structural" and "value" changes to further improve the change detection logic.

Example

const _ = require('lodash');

function detectConfigChanges(oldConfig, newConfig) {
  const changedPaths = [];

  if (!_.isEqual(oldConfig.plugins, newConfig.plugins)) {
    const changedPluginPaths = findChangedPaths(oldConfig.plugins.entries, newConfig.plugins.entries);
    changedPaths.push(...changedPluginPaths);
  }

  return changedPaths;
}

Notes

  • The provided pseudocode and suggested fixes are based on the assumption that the issue is related to the change detection logic.
  • Further investigation and testing may be necessary to confirm the root cause and ensure the proposed solution works as expected.
  • The findChangedPaths function is not implemented in the example, but it should recursively compare the plugin entries and return an array of changed paths.

Recommendation

Apply the workaround of updating the change detection logic to use deep equality comparison, as it is likely to fix the issue and improve the overall reliability of the config change detection mechanism.

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

openclaw - 💡(How to fix) Fix [P0] Config change detection bug: Any modification triggers gateway restart [1 comments, 2 participants]