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.
|
- 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
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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 doesn’t 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`, Telegram’s 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 `"*"`
|
||||||
|
|||||||
@@ -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.)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user