feat(status): add Telegram/WhatsApp troubleshooting warnings

This commit is contained in:
Peter Steinberger
2026-01-08 23:14:11 +01:00
parent a33271d374
commit cd2f3bd355
8 changed files with 196 additions and 25 deletions

View File

@@ -36,6 +36,7 @@
- CLI: restore hidden `gateway-daemon` alias for legacy launchd configs. - CLI: restore hidden `gateway-daemon` alias for legacy launchd configs.
- Control UI: show skill install progress + per-skill results, hide install once binaries present. (#445) — thanks @pkrmf - Control UI: show skill install progress + per-skill results, hide install once binaries present. (#445) — thanks @pkrmf
- Providers/Doctor: surface Discord privileged intent (Message Content) misconfiguration with actionable warnings. - Providers/Doctor: surface Discord privileged intent (Message Content) misconfiguration with actionable warnings.
- Providers/Doctor: warn when Telegram config expects unmentioned group messages but Bot API privacy mode is likely enabled; surface WhatsApp login/disconnect hints.
## 2026.1.8 ## 2026.1.8

View File

@@ -203,6 +203,7 @@ Manage chat provider accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage).
Subcommands: Subcommands:
- `providers list`: show configured chat providers and auth profiles (Claude Code + Codex CLI OAuth sync included). - `providers list`: show configured chat providers and auth profiles (Claude Code + Codex CLI OAuth sync included).
- `providers status`: check gateway reachability and provider health (`--probe` to verify credentials; use `status --deep` for local-only probes). - `providers status`: check gateway reachability and provider health (`--probe` to verify credentials; use `status --deep` for local-only probes).
- Tip: `providers status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `clawdbot doctor`).
- `providers add`: wizard-style setup when no flags are passed; flags switch to non-interactive mode. - `providers add`: wizard-style setup when no flags are passed; flags switch to non-interactive mode.
- `providers remove`: disable by default; pass `--delete` to remove config entries without prompts. - `providers remove`: disable by default; pass `--delete` to remove config entries without prompts.
- `providers login`: interactive provider login (WhatsApp Web only). - `providers login`: interactive provider login (WhatsApp Web only).

View File

@@ -228,10 +228,11 @@ Outbound Telegram API calls retry on transient network/429 errors with exponenti
## Troubleshooting ## Troubleshooting
**Bot doesn't respond to non-mention messages in group:** **Bot doesnt respond to non-mention messages in a group:**
- Check if group is in `telegram.groups` with `requireMention: false` - If you set `telegram.groups.*.requireMention=false`, Telegrams Bot API **privacy mode** must be disabled.
- Or use `"*": { "requireMention": false }` to enable for all groups - BotFather: `/setprivacy`**Disable** (then remove + re-add the bot to the group)
- Test with `/activation always` command (requires config change to persist) - `clawdbot providers status` shows a warning when config expects unmentioned group messages.
- Quick test: `/activation always` (session-only; use config for persistence)
**Bot not seeing group messages at all:** **Bot not seeing group messages at all:**
- If `telegram.groups` is set, the group must be listed or use `"*"` - If `telegram.groups` is set, the group must be listed or use `"*"`

View File

@@ -188,3 +188,16 @@ Recommended for personal numbers:
- Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`. - Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`.
- Log file: `/tmp/clawdbot/clawdbot-YYYY-MM-DD.log` (configurable). - Log file: `/tmp/clawdbot/clawdbot-YYYY-MM-DD.log` (configurable).
- Troubleshooting guide: [`docs/troubleshooting.md`](/gateway/troubleshooting). - Troubleshooting guide: [`docs/troubleshooting.md`](/gateway/troubleshooting).
## Troubleshooting (quick)
**Not linked / QR login required**
- Symptom: `providers status` shows `linked: false` or warns “Not linked”.
- Fix: run `clawdbot providers login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices).
**Linked but disconnected / reconnect loop**
- Symptom: `providers status` shows `running, disconnected` or warns “Linked but disconnected”.
- Fix: `clawdbot doctor` (or restart the gateway). If it persists, relink via `providers login` and inspect `clawdbot logs --follow`.
**Bun runtime**
- WhatsApp uses Baileys; run the gateway with **Node** for WhatsApp. (See Getting Started runtime note.)

View File

@@ -339,4 +339,42 @@ describe("providers command", () => {
expect(lines.join("\n")).toMatch(/Message Content Intent is limited/i); expect(lines.join("\n")).toMatch(/Message Content Intent is limited/i);
expect(lines.join("\n")).toMatch(/Run: clawdbot doctor/); expect(lines.join("\n")).toMatch(/Run: clawdbot doctor/);
}); });
it("surfaces Telegram privacy-mode hints when allowUnmentionedGroups is enabled", () => {
const lines = formatGatewayProvidersStatusLines({
telegramAccounts: [
{
accountId: "default",
enabled: true,
configured: true,
allowUnmentionedGroups: true,
},
],
});
expect(lines.join("\n")).toMatch(/Warnings:/);
expect(lines.join("\n")).toMatch(/Telegram Bot API privacy mode/i);
});
it("surfaces WhatsApp auth/runtime hints when unlinked or disconnected", () => {
const unlinked = formatGatewayProvidersStatusLines({
whatsappAccounts: [{ accountId: "default", enabled: true, linked: false }],
});
expect(unlinked.join("\n")).toMatch(/WhatsApp/i);
expect(unlinked.join("\n")).toMatch(/Not linked/i);
const disconnected = formatGatewayProvidersStatusLines({
whatsappAccounts: [
{
accountId: "default",
enabled: true,
linked: true,
running: true,
connected: false,
reconnectAttempts: 5,
lastError: "connection closed",
},
],
});
expect(disconnected.join("\n")).toMatch(/disconnected/i);
});
}); });

View File

@@ -75,6 +75,9 @@ export function formatGatewayProvidersStatusLines(
if (typeof account.running === "boolean") { if (typeof account.running === "boolean") {
bits.push(account.running ? "running" : "stopped"); bits.push(account.running ? "running" : "stopped");
} }
if (typeof account.connected === "boolean") {
bits.push(account.connected ? "connected" : "disconnected");
}
if (typeof account.mode === "string" && account.mode.length > 0) { if (typeof account.mode === "string" && account.mode.length > 0) {
bits.push(`mode:${account.mode}`); bits.push(`mode:${account.mode}`);
} }
@@ -110,6 +113,9 @@ export function formatGatewayProvidersStatusLines(
) { ) {
bits.push(`intents:content=${messageContent}`); bits.push(`intents:content=${messageContent}`);
} }
if (account.allowUnmentionedGroups === true) {
bits.push("groups:unmentioned");
}
if (typeof account.baseUrl === "string" && account.baseUrl) { if (typeof account.baseUrl === "string" && account.baseUrl) {
bits.push(`url:${account.baseUrl}`); bits.push(`url:${account.baseUrl}`);
} }

View File

@@ -97,6 +97,17 @@ export const providersHandlers: GatewayRequestHandlers = {
); );
lastProbeAt = Date.now(); lastProbeAt = Date.now();
} }
const groups =
cfg.telegram?.accounts?.[account.accountId]?.groups ?? cfg.telegram?.groups;
const allowUnmentionedGroups =
Boolean(groups?.["*"] && (groups["*"] as { requireMention?: boolean }).requireMention === false) ||
Object.entries(groups ?? {}).some(
([key, value]) =>
key !== "*" &&
Boolean(value) &&
typeof value === "object" &&
(value as { requireMention?: boolean }).requireMention === false,
);
return { return {
accountId: account.accountId, accountId: account.accountId,
name: account.name, name: account.name,
@@ -110,6 +121,7 @@ export const providersHandlers: GatewayRequestHandlers = {
lastError: rt?.lastError ?? null, lastError: rt?.lastError ?? null,
probe: telegramProbe, probe: telegramProbe,
lastProbeAt, lastProbeAt,
allowUnmentionedGroups,
}; };
}), }),
); );

View File

@@ -1,7 +1,7 @@
export type ProviderStatusIssue = { export type ProviderStatusIssue = {
provider: "discord"; provider: "discord" | "telegram" | "whatsapp";
accountId: string; accountId: string;
kind: "intent" | "permissions" | "config"; kind: "intent" | "permissions" | "config" | "auth" | "runtime";
message: string; message: string;
fix?: string; fix?: string;
}; };
@@ -21,6 +21,23 @@ type DiscordAccountStatus = {
application?: unknown; application?: unknown;
}; };
type TelegramAccountStatus = {
accountId?: unknown;
enabled?: unknown;
configured?: unknown;
allowUnmentionedGroups?: unknown;
};
type WhatsAppAccountStatus = {
accountId?: unknown;
enabled?: unknown;
linked?: unknown;
connected?: unknown;
running?: unknown;
reconnectAttempts?: unknown;
lastError?: unknown;
};
function asString(value: unknown): string | undefined { function asString(value: unknown): string | undefined {
return typeof value === "string" && value.trim().length > 0 return typeof value === "string" && value.trim().length > 0
? value.trim() ? value.trim()
@@ -57,34 +74,116 @@ function readDiscordApplicationSummary(value: unknown): DiscordApplicationSummar
}; };
} }
function readTelegramAccountStatus(value: unknown): TelegramAccountStatus | null {
if (!isRecord(value)) return null;
return {
accountId: value.accountId,
enabled: value.enabled,
configured: value.configured,
allowUnmentionedGroups: value.allowUnmentionedGroups,
};
}
function readWhatsAppAccountStatus(value: unknown): WhatsAppAccountStatus | null {
if (!isRecord(value)) return null;
return {
accountId: value.accountId,
enabled: value.enabled,
linked: value.linked,
connected: value.connected,
running: value.running,
reconnectAttempts: value.reconnectAttempts,
lastError: value.lastError,
};
}
export function collectProvidersStatusIssues( export function collectProvidersStatusIssues(
payload: Record<string, unknown>, payload: Record<string, unknown>,
): ProviderStatusIssue[] { ): ProviderStatusIssue[] {
const issues: ProviderStatusIssue[] = []; const issues: ProviderStatusIssue[] = [];
const discordAccountsRaw = payload.discordAccounts; const discordAccountsRaw = payload.discordAccounts;
if (!Array.isArray(discordAccountsRaw)) return issues; if (Array.isArray(discordAccountsRaw)) {
for (const entry of discordAccountsRaw) {
const account = readDiscordAccountStatus(entry);
if (!account) continue;
const accountId = asString(account.accountId) ?? "default";
const enabled = account.enabled !== false;
const configured = account.configured === true;
if (!enabled || !configured) continue;
for (const entry of discordAccountsRaw) { const app = readDiscordApplicationSummary(account.application);
const account = readDiscordAccountStatus(entry); const messageContent = app.intents?.messageContent;
if (!account) continue; if (messageContent && messageContent !== "enabled") {
const accountId = asString(account.accountId) ?? "default"; issues.push({
const enabled = account.enabled !== false; provider: "discord",
const configured = account.configured === true; accountId,
if (!enabled || !configured) continue; kind: "intent",
message: `Message Content Intent is ${messageContent}. Bot may not see normal channel messages.`,
fix: "Enable Message Content Intent in Discord Dev Portal → Bot → Privileged Gateway Intents, or require mention-only operation.",
});
}
}
}
const app = readDiscordApplicationSummary(account.application); const telegramAccountsRaw = payload.telegramAccounts;
const messageContent = app.intents?.messageContent; if (Array.isArray(telegramAccountsRaw)) {
if (messageContent && messageContent !== "enabled") { for (const entry of telegramAccountsRaw) {
issues.push({ const account = readTelegramAccountStatus(entry);
provider: "discord", if (!account) continue;
accountId, const accountId = asString(account.accountId) ?? "default";
kind: "intent", const enabled = account.enabled !== false;
message: `Message Content Intent is ${messageContent}. Bot may not see normal channel messages.`, const configured = account.configured === true;
fix: "Enable Message Content Intent in Discord Dev Portal → Bot → Privileged Gateway Intents, or require mention-only operation.", if (!enabled || !configured) continue;
}); if (account.allowUnmentionedGroups === true) {
issues.push({
provider: "telegram",
accountId,
kind: "config",
message:
"Config allows unmentioned group messages (requireMention=false). Telegram Bot API privacy mode will block most group messages unless disabled.",
fix: "In BotFather run /setprivacy → Disable for this bot (then restart the gateway).",
});
}
}
}
const whatsappAccountsRaw = payload.whatsappAccounts;
if (Array.isArray(whatsappAccountsRaw)) {
for (const entry of whatsappAccountsRaw) {
const account = readWhatsAppAccountStatus(entry);
if (!account) continue;
const accountId = asString(account.accountId) ?? "default";
const enabled = account.enabled !== false;
if (!enabled) continue;
const linked = account.linked === true;
const running = account.running === true;
const connected = account.connected === true;
const reconnectAttempts =
typeof account.reconnectAttempts === "number" ? account.reconnectAttempts : null;
const lastError = asString(account.lastError);
if (!linked) {
issues.push({
provider: "whatsapp",
accountId,
kind: "auth",
message: "Not linked (no WhatsApp Web session).",
fix: "Run: clawdbot providers login (scan QR on the gateway host).",
});
continue;
}
if (running && !connected) {
issues.push({
provider: "whatsapp",
accountId,
kind: "runtime",
message: `Linked but disconnected${reconnectAttempts != null ? ` (reconnectAttempts=${reconnectAttempts})` : ""}${lastError ? `: ${lastError}` : "."}`,
fix: "Run: clawdbot doctor (or restart the gateway). If it persists, relink via providers login and check logs.",
});
}
} }
} }
return issues; return issues;
} }