fix(telegram): skip polling webhook probe

This commit is contained in:
Peter Steinberger
2026-04-27 20:49:48 +01:00
parent 5a23032adb
commit d7c3a77b93
4 changed files with 39 additions and 21 deletions

View File

@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Channels/Telegram: skip the optional webhook-info API call during polling-mode status checks and startup bot-label probes so long-polling setups avoid an unnecessary Telegram round trip. Carries forward #72990. Thanks @danielgruneberg.
- Sessions: ignore future-dated session activity timestamps during reset freshness checks and cap future `updatedAt` values at the merge boundary so clock-skewed messages cannot keep stale sessions alive forever. Fixes #72989. Thanks @martingarramon.
- Plugins/CLI: allow managed plugin installs when the active extensions root is a symlink to a real state directory, while keeping nested target symlinks blocked and suppressing misleading hook-pack fallback errors for install-boundary failures. Fixes #72946. Thanks @mayank6136.
- Gateway/startup: keep hot Gateway boot paths on leaf config imports and add max-RSS reporting to the gateway startup bench so low-memory startup regressions are visible before release. Thanks @vincentkoc.

View File

@@ -769,6 +769,7 @@ export const telegramPlugin = createChatChannelPlugin({
proxyUrl: account.config.proxy,
network: account.config.network,
apiRoot: account.config.apiRoot,
includeWebhookInfo: Boolean(account.config.webhookUrl),
}),
formatCapabilitiesProbe: ({ probe }) => {
const lines = [];
@@ -885,6 +886,7 @@ export const telegramPlugin = createChatChannelPlugin({
proxyUrl: account.config.proxy,
network: account.config.network,
apiRoot: account.config.apiRoot,
includeWebhookInfo: false,
});
const username = probe.ok ? probe.bot?.username?.trim() : null;
if (username) {

View File

@@ -173,6 +173,18 @@ describe("probeTelegram retry logic", () => {
expect(fetchMock).toHaveBeenCalledTimes(1); // Should not retry
});
it("can skip webhook info when caller only needs bot identity", async () => {
const fetchMock = installFetchMock();
mockGetMeSuccess(fetchMock);
const result = await probeTelegram(token, timeoutMs, { includeWebhookInfo: false });
expect(result.ok).toBe(true);
expect(result.webhook).toBeUndefined();
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0]?.[0]).toBe("https://api.telegram.org/bottest-token/getMe");
});
it("uses resolver-scoped Telegram fetch with probe network options", async () => {
const fetchMock = installFetchMock();
mockGetMeSuccess(fetchMock);
@@ -192,7 +204,6 @@ describe("probeTelegram retry logic", () => {
autoSelectFamily: false,
dnsResultOrder: "ipv4first",
},
apiRoot: undefined,
});
});

View File

@@ -23,6 +23,7 @@ export type TelegramProbeOptions = {
network?: TelegramNetworkConfig;
accountId?: string;
apiRoot?: string;
includeWebhookInfo?: boolean;
};
const probeFetcherCache = new Map<string, typeof fetch>();
@@ -102,6 +103,7 @@ export async function probeTelegram(
const timeoutBudgetMs = Math.max(1, Math.floor(timeoutMs));
const deadlineMs = started + timeoutBudgetMs;
const options = resolveProbeOptions(proxyOrOptions);
const includeWebhookInfo = options?.includeWebhookInfo !== false;
const fetcher = resolveProbeFetcher(token, options);
const apiBase = resolveTelegramApiBase(options?.apiRoot);
const base = `${apiBase}/bot${token}`;
@@ -184,29 +186,31 @@ export async function probeTelegram(
: null,
};
// Try to fetch webhook info, but don't fail health if it errors.
try {
const webhookRemainingBudgetMs = resolveRemainingBudgetMs();
if (webhookRemainingBudgetMs > 0) {
const webhookRes = await fetchWithTimeout(
`${base}/getWebhookInfo`,
{},
Math.max(1, Math.min(timeoutBudgetMs, webhookRemainingBudgetMs)),
fetcher,
);
const webhookJson = (await webhookRes.json()) as {
ok?: boolean;
result?: { url?: string; has_custom_certificate?: boolean };
};
if (webhookRes.ok && webhookJson?.ok) {
result.webhook = {
url: webhookJson.result?.url ?? null,
hasCustomCert: webhookJson.result?.has_custom_certificate ?? null,
if (includeWebhookInfo) {
// Try to fetch webhook info, but don't fail health if it errors.
try {
const webhookRemainingBudgetMs = resolveRemainingBudgetMs();
if (webhookRemainingBudgetMs > 0) {
const webhookRes = await fetchWithTimeout(
`${base}/getWebhookInfo`,
{},
Math.max(1, Math.min(timeoutBudgetMs, webhookRemainingBudgetMs)),
fetcher,
);
const webhookJson = (await webhookRes.json()) as {
ok?: boolean;
result?: { url?: string; has_custom_certificate?: boolean };
};
if (webhookRes.ok && webhookJson?.ok) {
result.webhook = {
url: webhookJson.result?.url ?? null,
hasCustomCert: webhookJson.result?.has_custom_certificate ?? null,
};
}
}
} catch {
// ignore webhook errors for probe
}
} catch {
// ignore webhook errors for probe
}
result.ok = true;