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.
- 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: warn when Telegram config expects unmentioned group messages but Bot API privacy mode is likely enabled; surface WhatsApp login/disconnect hints.
## 2026.1.8

View File

@@ -203,6 +203,7 @@ Manage chat provider accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage).
Subcommands:
- `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).
- 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 remove`: disable by default; pass `--delete` to remove config entries without prompts.
- `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
**Bot doesn't respond to non-mention messages in group:**
- Check if group is in `telegram.groups` with `requireMention: false`
- Or use `"*": { "requireMention": false }` to enable for all groups
- Test with `/activation always` command (requires config change to persist)
**Bot doesnt respond to non-mention messages in a group:**
- If you set `telegram.groups.*.requireMention=false`, Telegrams Bot API **privacy mode** must be disabled.
- BotFather: `/setprivacy`**Disable** (then remove + re-add the bot to the group)
- `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:**
- 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`.
- Log file: `/tmp/clawdbot/clawdbot-YYYY-MM-DD.log` (configurable).
- 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(/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") {
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) {
bits.push(`mode:${account.mode}`);
}
@@ -110,6 +113,9 @@ export function formatGatewayProvidersStatusLines(
) {
bits.push(`intents:content=${messageContent}`);
}
if (account.allowUnmentionedGroups === true) {
bits.push("groups:unmentioned");
}
if (typeof account.baseUrl === "string" && account.baseUrl) {
bits.push(`url:${account.baseUrl}`);
}

View File

@@ -97,6 +97,17 @@ export const providersHandlers: GatewayRequestHandlers = {
);
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 {
accountId: account.accountId,
name: account.name,
@@ -110,6 +121,7 @@ export const providersHandlers: GatewayRequestHandlers = {
lastError: rt?.lastError ?? null,
probe: telegramProbe,
lastProbeAt,
allowUnmentionedGroups,
};
}),
);

View File

@@ -1,7 +1,7 @@
export type ProviderStatusIssue = {
provider: "discord";
provider: "discord" | "telegram" | "whatsapp";
accountId: string;
kind: "intent" | "permissions" | "config";
kind: "intent" | "permissions" | "config" | "auth" | "runtime";
message: string;
fix?: string;
};
@@ -21,6 +21,23 @@ type DiscordAccountStatus = {
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 {
return typeof value === "string" && value.trim().length > 0
? 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(
payload: Record<string, unknown>,
): ProviderStatusIssue[] {
const issues: ProviderStatusIssue[] = [];
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 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;
const app = readDiscordApplicationSummary(account.application);
const messageContent = app.intents?.messageContent;
if (messageContent && messageContent !== "enabled") {
issues.push({
provider: "discord",
accountId,
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 messageContent = app.intents?.messageContent;
if (messageContent && messageContent !== "enabled") {
issues.push({
provider: "discord",
accountId,
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 telegramAccountsRaw = payload.telegramAccounts;
if (Array.isArray(telegramAccountsRaw)) {
for (const entry of telegramAccountsRaw) {
const account = readTelegramAccountStatus(entry);
if (!account) continue;
const accountId = asString(account.accountId) ?? "default";
const enabled = account.enabled !== false;
const configured = account.configured === true;
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;
}