mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix(doctor): warn on missing channel env tokens
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Cron: make scheduler reload schedule comparison tolerate malformed persisted jobs, so one bad cron entry no longer aborts the whole tick. Fixes #75886. Thanks @samfox-ai.
|
||||
- Doctor/channels: warn after migrations when default Telegram or Discord accounts have no configured token and their env fallback (`TELEGRAM_BOT_TOKEN` or `DISCORD_BOT_TOKEN`) is unavailable, with secret-safe migration docs for checking state-dir `.env`. Fixes #74298. Thanks @lolaopenclaw.
|
||||
- Control UI/chat: keep live replies visible when a raw session alias such as `main` sends the chat turn but Gateway emits events under the canonical session key for the same run. Fixes #73716. Thanks @teebes.
|
||||
- CLI/models: reject `--agent` on `openclaw models set` and `set-image` instead of silently writing agent-scoped requests to global model defaults. Fixes #68391. Thanks @derrickabellard.
|
||||
- CLI: stop treating the legacy singular `openclaw tool ...` token as a plugin id under restrictive `plugins.allow`, so it falls through as a normal unknown/reserved command instead of suggesting a stale allowlist entry. Fixes #64732. Thanks @efe-arv, @SweetSophia, and @hashtag1974.
|
||||
|
||||
@@ -58,6 +58,7 @@ Notes:
|
||||
- If sandbox mode is enabled but Docker is unavailable, doctor reports a high-signal warning with remediation (`install Docker` or `openclaw config set agents.defaults.sandbox.mode off`).
|
||||
- If `gateway.auth.token`/`gateway.auth.password` are SecretRef-managed and unavailable in the current command path, doctor reports a read-only warning and does not write plaintext fallback credentials.
|
||||
- If channel SecretRef inspection fails in a fix path, doctor continues and reports a warning instead of exiting early.
|
||||
- After state-directory migrations, doctor warns when enabled default Telegram or Discord accounts depend on env fallback and `TELEGRAM_BOT_TOKEN` or `DISCORD_BOT_TOKEN` is unavailable to the doctor process.
|
||||
- Telegram `allowFrom` username auto-resolution (`doctor --fix`) requires a resolvable Telegram token in the current command path. If token inspection is unavailable, doctor reports a warning and skips auto-resolution for that pass.
|
||||
|
||||
## macOS: `launchctl` env overrides
|
||||
|
||||
@@ -82,6 +82,14 @@ Run `openclaw status` on the old machine to confirm your state directory path. C
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
If Telegram or Discord uses the default env fallback (`TELEGRAM_BOT_TOKEN` or `DISCORD_BOT_TOKEN`), verify the migrated state-dir `.env` contains those keys without printing the secret values:
|
||||
|
||||
```bash
|
||||
awk -F= '/^(TELEGRAM_BOT_TOKEN|DISCORD_BOT_TOKEN)=/ { print $1 "=present" }' ~/.openclaw/.env
|
||||
```
|
||||
|
||||
`openclaw doctor` also warns when an enabled default Telegram or Discord account has no configured token and the matching env variable is unavailable to the doctor process.
|
||||
|
||||
### Common pitfalls
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
collectDiscordMissingEnvTokenWarnings,
|
||||
collectDiscordNumericIdWarnings,
|
||||
discordDoctor,
|
||||
maybeRepairDiscordNumericIds,
|
||||
@@ -361,4 +362,44 @@ describe("discord doctor", () => {
|
||||
expect(warnings[0]).toContain("cannot be auto-repaired");
|
||||
expect(warnings[1]).toContain("openclaw doctor --fix");
|
||||
});
|
||||
|
||||
it("warns when default env fallback token is missing after migration", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
discord: {
|
||||
allowFrom: ["123"],
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expect(collectDiscordMissingEnvTokenWarnings({ cfg, env: {} })).toEqual([
|
||||
expect.stringContaining("DISCORD_BOT_TOKEN is absent"),
|
||||
]);
|
||||
expect(
|
||||
collectDiscordMissingEnvTokenWarnings({ cfg, env: { DISCORD_BOT_TOKEN: "Bot tok" } }),
|
||||
).toEqual([]);
|
||||
expect(
|
||||
await discordDoctor.collectPreviewWarnings?.({
|
||||
cfg,
|
||||
doctorFixCommand: "openclaw doctor --fix",
|
||||
env: {},
|
||||
}),
|
||||
).toEqual([expect.stringContaining("DISCORD_BOT_TOKEN is absent")]);
|
||||
});
|
||||
|
||||
it("does not warn about DISCORD_BOT_TOKEN when a non-default account is selected", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
discord: {
|
||||
accounts: {
|
||||
work: {
|
||||
token: "Bot work-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expect(collectDiscordMissingEnvTokenWarnings({ cfg, env: {} })).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import { type ChannelDoctorAdapter } from "openclaw/plugin-sdk/channel-contract"
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { collectProviderDangerousNameMatchingScopes } from "openclaw/plugin-sdk/runtime-doctor";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { inspectDiscordAccount } from "./account-inspect.js";
|
||||
import { resolveDefaultDiscordAccountId } from "./accounts.js";
|
||||
import { normalizeCompatibilityConfig as normalizeDiscordCompatibilityConfig } from "./doctor-contract.js";
|
||||
import { DISCORD_LEGACY_CONFIG_RULES } from "./doctor-shared.js";
|
||||
import { isDiscordMutableAllowEntry } from "./security-doctor.js";
|
||||
@@ -235,6 +237,26 @@ export function maybeRepairDiscordNumericIds(
|
||||
};
|
||||
}
|
||||
|
||||
export function collectDiscordMissingEnvTokenWarnings(params: {
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): string[] {
|
||||
if (resolveDefaultDiscordAccountId(params.cfg) !== "default") {
|
||||
return [];
|
||||
}
|
||||
const account = inspectDiscordAccount({
|
||||
cfg: params.cfg,
|
||||
accountId: "default",
|
||||
envToken: params.env?.DISCORD_BOT_TOKEN ?? "",
|
||||
});
|
||||
if (!account.enabled || account.tokenStatus !== "missing" || account.tokenSource !== "none") {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
"- channels.discord: default account has no available bot token, and DISCORD_BOT_TOKEN is absent in this doctor environment. After migration, verify DISCORD_BOT_TOKEN is present in the state-dir .env or configure channels.discord.token / channels.discord.accounts.default.token as a SecretRef.",
|
||||
];
|
||||
}
|
||||
|
||||
function collectDiscordMutableAllowlistWarnings(cfg: OpenClawConfig): string[] {
|
||||
const hits: Array<{ path: string; entry: string }> = [];
|
||||
const addHits = (pathLabel: string, list: unknown) => {
|
||||
@@ -306,11 +328,13 @@ export const discordDoctor: ChannelDoctorAdapter = {
|
||||
warnOnEmptyGroupSenderAllowlist: false,
|
||||
legacyConfigRules: DISCORD_LEGACY_CONFIG_RULES,
|
||||
normalizeCompatibilityConfig: normalizeDiscordCompatibilityConfig,
|
||||
collectPreviewWarnings: ({ cfg, doctorFixCommand }) =>
|
||||
collectDiscordNumericIdWarnings({
|
||||
collectPreviewWarnings: ({ cfg, doctorFixCommand, env }) => [
|
||||
...collectDiscordMissingEnvTokenWarnings({ cfg, env }),
|
||||
...collectDiscordNumericIdWarnings({
|
||||
hits: scanDiscordNumericIdEntries(cfg),
|
||||
doctorFixCommand,
|
||||
}),
|
||||
],
|
||||
collectMutableAllowlistWarnings: ({ cfg }) => collectDiscordMutableAllowlistWarnings(cfg),
|
||||
repairConfig: ({ cfg, doctorFixCommand }) => maybeRepairDiscordNumericIds(cfg, doctorFixCommand),
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
collectTelegramApiRootWarnings,
|
||||
collectTelegramEmptyAllowlistExtraWarnings,
|
||||
collectTelegramGroupPolicyWarnings,
|
||||
collectTelegramMissingEnvTokenWarnings,
|
||||
maybeRepairTelegramApiRoots,
|
||||
maybeRepairTelegramAllowFromUsernames,
|
||||
scanTelegramBotEndpointApiRoots,
|
||||
@@ -62,7 +63,7 @@ describe("telegram doctor", () => {
|
||||
enabled: true,
|
||||
token: "tok",
|
||||
tokenSource: "config",
|
||||
tokenStatus: "configured",
|
||||
tokenStatus: "available",
|
||||
});
|
||||
lookupTelegramChatIdMock.mockReset();
|
||||
});
|
||||
@@ -355,4 +356,70 @@ describe("telegram doctor", () => {
|
||||
"- channels.telegram.apiRoot: removed trailing /bot<TOKEN> from Telegram apiRoot.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("warns when default env fallback token is missing after migration", async () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
allowFrom: ["123"],
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
inspectTelegramAccountMock.mockReturnValueOnce({
|
||||
enabled: true,
|
||||
token: "",
|
||||
tokenSource: "none",
|
||||
tokenStatus: "missing",
|
||||
configured: false,
|
||||
config: {},
|
||||
});
|
||||
expect(collectTelegramMissingEnvTokenWarnings({ cfg, env: {} })).toEqual([
|
||||
expect.stringContaining("TELEGRAM_BOT_TOKEN is absent"),
|
||||
]);
|
||||
|
||||
inspectTelegramAccountMock.mockReturnValueOnce({
|
||||
enabled: true,
|
||||
token: "123:tok",
|
||||
tokenSource: "env",
|
||||
tokenStatus: "available",
|
||||
configured: true,
|
||||
config: {},
|
||||
});
|
||||
expect(
|
||||
collectTelegramMissingEnvTokenWarnings({ cfg, env: { TELEGRAM_BOT_TOKEN: "123:tok" } }),
|
||||
).toEqual([]);
|
||||
|
||||
inspectTelegramAccountMock.mockReturnValueOnce({
|
||||
enabled: true,
|
||||
token: "",
|
||||
tokenSource: "none",
|
||||
tokenStatus: "missing",
|
||||
configured: false,
|
||||
config: {},
|
||||
});
|
||||
expect(
|
||||
await telegramDoctor.collectPreviewWarnings?.({
|
||||
cfg,
|
||||
doctorFixCommand: "openclaw doctor --fix",
|
||||
env: {},
|
||||
}),
|
||||
).toContainEqual(expect.stringContaining("TELEGRAM_BOT_TOKEN is absent"));
|
||||
});
|
||||
|
||||
it("does not warn about TELEGRAM_BOT_TOKEN when a non-default account is selected", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
telegram: {
|
||||
accounts: {
|
||||
work: {
|
||||
botToken: "123:work",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expect(collectTelegramMissingEnvTokenWarnings({ cfg, env: {} })).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,11 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { inspectTelegramAccount } from "./account-inspect.js";
|
||||
import { listTelegramAccountIds, resolveTelegramAccount } from "./accounts.js";
|
||||
import {
|
||||
listTelegramAccountIds,
|
||||
resolveDefaultTelegramAccountId,
|
||||
resolveTelegramAccount,
|
||||
} from "./accounts.js";
|
||||
import { isNumericTelegramSenderUserId, normalizeTelegramAllowFromEntry } from "./allow-from.js";
|
||||
import { lookupTelegramChatId } from "./api-fetch.js";
|
||||
import { hasTelegramBotEndpointApiRoot, normalizeTelegramApiRoot } from "./api-root.js";
|
||||
@@ -224,6 +228,26 @@ export function maybeRepairTelegramApiRoots(cfg: OpenClawConfig): {
|
||||
};
|
||||
}
|
||||
|
||||
export function collectTelegramMissingEnvTokenWarnings(params: {
|
||||
cfg: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): string[] {
|
||||
if (resolveDefaultTelegramAccountId(params.cfg) !== "default") {
|
||||
return [];
|
||||
}
|
||||
const account = inspectTelegramAccount({
|
||||
cfg: params.cfg,
|
||||
accountId: "default",
|
||||
envToken: params.env?.TELEGRAM_BOT_TOKEN ?? "",
|
||||
});
|
||||
if (!account.enabled || account.tokenStatus !== "missing" || account.tokenSource !== "none") {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
"- channels.telegram: default account has no available bot token, and TELEGRAM_BOT_TOKEN is absent in this doctor environment. After migration, verify TELEGRAM_BOT_TOKEN is present in the state-dir .env or configure channels.telegram.botToken / channels.telegram.accounts.default.botToken as a SecretRef.",
|
||||
];
|
||||
}
|
||||
|
||||
async function repairTelegramConfig(params: { cfg: OpenClawConfig }): Promise<{
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
@@ -472,7 +496,8 @@ export function collectTelegramEmptyAllowlistExtraWarnings(
|
||||
export const telegramDoctor: ChannelDoctorAdapter = {
|
||||
legacyConfigRules: TELEGRAM_LEGACY_CONFIG_RULES,
|
||||
normalizeCompatibilityConfig: normalizeTelegramCompatibilityConfig,
|
||||
collectPreviewWarnings: ({ cfg, doctorFixCommand }) => [
|
||||
collectPreviewWarnings: ({ cfg, doctorFixCommand, env }) => [
|
||||
...collectTelegramMissingEnvTokenWarnings({ cfg, env }),
|
||||
...collectTelegramInvalidAllowFromWarnings({
|
||||
hits: scanTelegramInvalidAllowFromEntries(cfg),
|
||||
doctorFixCommand,
|
||||
|
||||
@@ -524,6 +524,7 @@ export type ChannelDoctorAdapter = {
|
||||
collectPreviewWarnings?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
doctorFixCommand: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) => string[] | Promise<string[]>;
|
||||
collectMutableAllowlistWarnings?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
|
||||
Reference in New Issue
Block a user