feat(status): add Telegram/WhatsApp troubleshooting warnings
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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 doesn’t respond to non-mention messages in a group:**
|
||||
- If you set `telegram.groups.*.requireMention=false`, Telegram’s 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 `"*"`
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user