fix(channels): ignore persisted auth for auto-enable

This commit is contained in:
Peter Steinberger
2026-04-27 20:33:34 +01:00
parent dec1f68d7e
commit f7d67b8ea8
6 changed files with 37 additions and 23 deletions

View File

@@ -1255,6 +1255,7 @@ Docs: https://docs.openclaw.ai
- Amazon Bedrock/prompt caching: resolve opaque application inference profile targets before injecting Bedrock cache points, require every routed target to support explicit cache points, and retry transient profile lookups instead of caching a false negative for the rest of the process. (#69953) Thanks @anirudhmarc and @vincentkoc.
- Gateway/channel health: base stale-socket recovery on provider-proven transport activity instead of inbound app-event freshness, preventing quiet Slack, Discord, Telegram, Matrix, and local-style channels from being restarted solely because no user traffic arrived. (#69833) Thanks @bek91.
- OpenCode Go: canonicalize stale bundled `opencode-go` base URLs from `/go` or `/go/v1` to `/zen/go` or `/zen/go/v1`, so older generated model metadata stops hitting the 404 HTML endpoint. (#69898).
- Plugins/channels: keep persisted channel auth state out of configured-channel and plugin auto-enable detection, so stale WhatsApp credentials alone no longer trigger plugin activation or runtime dependency repair. (#72836, #72844) Thanks @xiao398008 and @haishmg.
- CLI/channels: honor `channels.<id>.enabled=false` as a hard read-only presence opt-out, so env vars, manifest env vars, or stale persisted auth state no longer make disabled channel plugins appear in status, doctor, or setup-only discovery.
- Channels/preview streaming: centralize draft-preview finalization so Slack, Discord, Mattermost, and Matrix no longer flush temporary preview messages for media/error finals, and preserve first-reply threading for normal fallback delivery.
- Discord: keep slash command follow-up chunks ephemeral when the command is configured for ephemeral replies, so long `/status` output no longer leaks fallback model or runtime details into the public channel. (#69869) thanks @gumadeiras.

View File

@@ -1031,10 +1031,12 @@ module:
}
```
Use it when setup, doctor, or configured-state flows need a cheap yes/no auth
probe before the full channel plugin loads. The target export should be a small
function that reads persisted state only; do not route it through the full
channel runtime barrel.
Use it when setup, doctor, status, or read-only presence flows need a cheap
yes/no auth probe before the full channel plugin loads. Persisted auth state is
not configured channel state: do not use this metadata to auto-enable plugins,
repair runtime dependencies, or decide whether a channel runtime should load.
The target export should be a small function that reads persisted state only; do
not route it through the full channel runtime barrel.
`openclaw.channel.configuredState` follows the same shape for cheap env-only
configured checks:

View File

@@ -25,16 +25,6 @@ vi.mock("../channels/plugins/configured-state.js", () => ({
},
}));
vi.mock("../channels/plugins/persisted-auth-state.js", () => ({
hasBundledChannelPersistedAuthState: ({
channelId,
env,
}: {
channelId: string;
env?: NodeJS.ProcessEnv;
}) => channelId === "matrix" && env?.OPENCLAW_STATE_DIR === "state-with-matrix-creds",
}));
vi.mock("../channels/plugins/bootstrap-registry.js", () => ({
getBootstrapChannelPlugin: () => undefined,
}));
@@ -78,9 +68,9 @@ describe("isChannelConfigured", () => {
).toBe(true);
});
it("detects persisted Matrix credentials through package metadata", () => {
it("does not treat persisted Matrix credentials as configured channel state", () => {
expect(
isChannelConfigured({}, "matrix", { OPENCLAW_STATE_DIR: "state-with-matrix-creds" }),
).toBe(true);
).toBe(false);
});
});

View File

@@ -1,6 +1,5 @@
import { getBootstrapChannelPlugin } from "../channels/plugins/bootstrap-registry.js";
import { hasBundledChannelConfiguredState } from "../channels/plugins/configured-state.js";
import { hasBundledChannelPersistedAuthState } from "../channels/plugins/persisted-auth-state.js";
import {
hasMeaningfulChannelConfigShallow,
resolveChannelConfigRecord,
@@ -18,10 +17,6 @@ export function isChannelConfigured(
if (hasBundledChannelConfiguredState({ channelId, cfg, env })) {
return true;
}
const pluginPersistedAuthState = hasBundledChannelPersistedAuthState({ channelId, cfg, env });
if (pluginPersistedAuthState) {
return true;
}
const plugin = getBootstrapChannelPlugin(channelId);
return Boolean(plugin?.config?.hasConfiguredState?.({ cfg, env }));
}

View File

@@ -1,4 +1,5 @@
import fs from "node:fs";
import path from "node:path";
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
import {
applyPluginAutoEnable,
@@ -556,6 +557,31 @@ describe("applyPluginAutoEnable core", () => {
expect(validateConfigObject(result.config).ok).toBe(true);
});
it("does not auto-enable WhatsApp from persisted auth state alone", () => {
const persistedEnv = makeIsolatedEnv();
const authDir = path.join(
persistedEnv.OPENCLAW_STATE_DIR ?? "",
"credentials",
"whatsapp",
"default",
);
fs.mkdirSync(authDir, { recursive: true });
fs.writeFileSync(path.join(authDir, "creds.json"), "{}", "utf-8");
const candidates = detectPluginAutoEnableCandidates({
config: {},
env: persistedEnv,
});
const result = applyPluginAutoEnable({
config: {},
env: persistedEnv,
});
expect(candidates).toEqual([]);
expect(result.config).toEqual({});
expect(result.changes).toEqual([]);
});
it("preserves configured plugin entries in restrictive plugins.allow", () => {
const result = materializePluginAutoEnableCandidates({
config: {

View File

@@ -283,7 +283,7 @@ function collectPluginIdsForConfiguredChannel(
}
function collectCandidateChannelIds(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): string[] {
return listPotentialConfiguredChannelIds(cfg, env).map(
return listPotentialConfiguredChannelIds(cfg, env, { includePersistedAuthState: false }).map(
(channelId) => normalizeChatChannelId(channelId) ?? channelId,
);
}
@@ -499,7 +499,7 @@ export function configMayNeedPluginAutoEnable(
if (hasConfiguredPluginConfigEntry(cfg)) {
return true;
}
if (hasPotentialConfiguredChannels(cfg, env)) {
if (hasPotentialConfiguredChannels(cfg, env, { includePersistedAuthState: false })) {
return true;
}
if (hasConfiguredProviderModelOrHarness(cfg, env)) {