Skip to content

openclaw doctor --fix corrupts signal multi-account config by inventing a phantom accounts.default block #62763

@tmote

Description

@tmote

Bug: openclaw doctor --fix corrupts signal multi-account config by inventing a phantom accounts.default block

Environment

  • OpenClaw 2026.4.5 (3e72c03)
  • Ubuntu 24.04.4 LTS, Node v24.14.1 (via nvm)
  • signal-cli 0.14.2 (native build) at /usr/local/bin/signal-cli
  • Two registered Signal accounts in multi-account configuration

Steps to reproduce

  1. Configure channels.signal with two named accounts and channel-level shared defaults:

    {
      "channels": {
        "signal": {
          "enabled": true,
          "cliPath": "signal-cli",
          "dmPolicy": "pairing",
          "groupPolicy": "disabled",
          "defaultAccount": "pleres",
          "accounts": {
            "pleres": {
              "account": "+15555550100",
              "name": "Account A"
            },
            "mote": {
              "account": "+15555550101",
              "name": "Account B",
              "httpPort": 8081
            }
          }
        }
      }
    }
  2. Verify the gateway is healthy with this config — both signal-cli daemons spawn cleanly on 127.0.0.1:8080 and 127.0.0.1:8081, both probes pass openclaw doctor (Signal: ok).

  3. Run openclaw doctor --fix.

  4. Doctor reports: "Moved channels.signal single-account top-level values into channels.signal.accounts.default." It writes a new openclaw.json with the following transformation:

    {
      "channels": {
        "signal": {
          "enabled": true,
          "defaultAccount": "pleres",
          "accounts": {
            "pleres": { "account": "+15555550100", "name": "Account A" },
            "mote":   { "account": "+15555550101", "name": "Account B", "httpPort": 8081 },
            "default": {
              "cliPath": "signal-cli",
              "dmPolicy": "pairing",
              "groupPolicy": "disabled"
            }
          }
        }
      }
    }
  5. The accounts.default block has no account (phone number) field. It is a settings holder masquerading as an account.

  6. Restart the gateway (or wait for hot reload).

Expected behavior

Either:

  • (A) Doctor leaves the existing config alone — top-level cliPath/dmPolicy/groupPolicy are valid channel-level defaults that named accounts can inherit, and the structure works in production.
  • (B) If a transformation IS desired, doctor should move the shared defaults INTO each named account block (accounts.pleres.dmPolicy, accounts.mote.dmPolicy, etc.), not invent a phantom accounts.default slot that has no real signal-cli account.

Actual behavior

After the transformation:

  • Static config validation passes (openclaw config validate reports OK).
  • Runtime breaks: gateway enters a crash loop. signal-cli daemons fail to spawn (the accounts.default block has no account field, the daemon-spawn pathway can't reconcile it).
  • Reverting openclaw.json to the pre---fix shape restores function.

Severity

High. openclaw doctor --fix is presented as the canonical "apply changes" path, and users will run it. The transformation causes complete signal channel outage with no actionable error. We had to back out by restoring an openclaw.json.bak from before the --fix run.

Workaround

Do not run openclaw doctor --fix on multi-account signal configs. Read-only openclaw doctor (without --fix) is fine for diagnostics; only the auto-apply path is dangerous. Apply doctor's recommendations manually after reading the proposed diff.

Suggested fix paths

  1. Detect that named accounts already exist; in that case, skip the "move single-account values" migration entirely (it's targeting a different config shape).
  2. If a settings-only inheritance block is desired, give it a different schema key (e.g. channels.signal.accountDefaults) so it doesn't collide with real accounts.
  3. Preserve top-level dmPolicy/groupPolicy/cliPath as legitimate channel-level defaults that all accounts inherit unless overridden — that's what the multi-account doc strongly implies should work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions