After installing the official openclaw-lark plugin, the system had two Feishu plugins coexisting:
The two plugins conflicted, creating a deadlock:
feishu.enabled = true: all three accounts (Tuoxie/Reba/Aobing) connected normally, but all Feishu tool calls threw "legacy plugin not disabled" errorsfeishu.enabled = false: tools worked, but the main account (Tuoxie) WebSocket never started — no incoming messagesWorse: both plugins competed for the feishu channel id. Different agents ended up on different reply paths — Reba and Aobing got streaming card output (typewriter effect), Tuoxie got static output (long wait, then all at once).
In src/core/tool-client.js, lines 111-119:
const feishuEntry = this.config.plugins?.entries?.feishu;
if (feishuEntry && feishuEntry.enabled !== false) {
throw new Error('❌ Legacy plugin is not disabled...');
}
As long as the built-in plugin isn't explicitly disabled, every openclaw-lark tool call throws immediately. Intended to force migration, but didn't account for the built-in plugin serving other purposes.
In src/core/accounts.js, getLarkAccount() has an implicit design rule: the default account's credentials must be at the top-level of channels.feishu, not under accounts.default.
const accountOverride = accountMap && requestedId !== DEFAULT_ACCOUNT_ID
? accountMap[requestedId]
: undefined; // default account never reads accounts.default!
Other accounts (media, aobing) go through accountMap[requestedId] normally. But the default account always falls through to the top-level only.
So when feishu.enabled = false and openclaw-lark takes over, if appId/appSecret are under accounts.default (the intuitive location), the default account gets configured = false, is skipped by getEnabledLarkAccounts(), and its WebSocket never starts — silently.
Two steps:
Step 1: Disable the built-in plugin
cat ~/.openclaw/openclaw.json | \
jq '.plugins.entries.feishu.enabled = false' \
> /tmp/oc.json && mv /tmp/oc.json ~/.openclaw/openclaw.json
Step 2: Promote default account credentials to the top level
cat ~/.openclaw/openclaw.json | jq '
.channels.feishu.appId = .channels.feishu.accounts.default.appId |
.channels.feishu.appSecret = .channels.feishu.accounts.default.appSecret |
.channels.feishu.dmPolicy = "open" |
.channels.feishu.groupPolicy = "open" |
.channels.feishu.allowFrom = ["*"]
' > /tmp/oc.json && mv /tmp/oc.json ~/.openclaw/openclaw.json
Restart the Gateway. Done.
Framework-level "implicit conventions" are nearly impossible to discover from docs alone. Both traps were only found by reading the source. When "the config looks right but it just doesn't work," read the source — don't keep tweaking parameters.
Also: when two plugins compete for the same channel id, whoever wins is non-deterministic. In multi-account setups, different agents may end up on different code paths, behaving completely differently. Very hard to debug.