fix(channels): surface partial status failures (#122349)

This commit is contained in:
Peter Steinberger
2026-08-11 17:18:26 -07:00
committed by GitHub
parent 964c8c84c1
commit 3254ececf1
4 changed files with 69 additions and 1 deletions
@@ -111,6 +111,18 @@ describe("channels command", () => {
expect(lines.join("\n")).toMatch(/eventLoopDelayMaxMs=62000/);
});
it("surfaces top-level partial status warnings", () => {
const lines = formatGatewayChannelsStatusLines({
partial: true,
warnings: ["whatsapp:default status failed: snapshot failed"],
channelLabels: {},
channelAccounts: {},
});
expect(lines.join("\n")).toMatch(/Channel status is partial/);
expect(lines.join("\n")).toContain("whatsapp:default status failed: snapshot failed");
});
it("surfaces transport liveness timestamps in channels status output", () => {
const lines = formatGatewayChannelsStatusLines({
channelLabels: {
+14
View File
@@ -76,6 +76,20 @@ export function formatGatewayChannelsStatusLines(payload: Record<string, unknown
if (eventLoopLine) {
lines.push(theme.warn(`Gateway event loop degraded ${eventLoopLine}`));
}
const statusWarnings = Array.isArray(payload.warnings)
? payload.warnings
.filter(
(warning): warning is string => typeof warning === "string" && warning.trim().length > 0,
)
.slice(0, 50)
: [];
if (payload.partial === true || statusWarnings.length > 0) {
lines.push(theme.warn("Channel status is partial:"));
for (const warning of statusWarnings) {
lines.push(`- ${warning.slice(0, 500)}`);
}
lines.push("");
}
const channelLabels =
payload.channelLabels && typeof payload.channelLabels === "object"
? (payload.channelLabels as Record<string, unknown>)
@@ -357,6 +357,40 @@ describe("channelsHandlers channels.status", () => {
expect(String(accountProbe.error)).toContain("probe failed");
});
it("marks account snapshot failures partial", async () => {
mocks.resolveChannelAccountSnapshot.mockRejectedValue(new Error("snapshot failed"));
const payload = await runChannelsStatus({ probe: false, timeoutMs: 1000 });
expect(payload.partial).toBe(true);
expect(payload.warnings).toEqual(["whatsapp:default status failed: Error: snapshot failed"]);
const channels = requireGatewayRecord(payload.channels, "channels payload");
expect(channels.whatsapp).toEqual({ configured: false });
});
it("isolates a failed channel status task while a sibling succeeds", async () => {
const broken = createChannelPlugin({ id: "broken" });
broken.config.listAccountIds = () => {
throw new Error("channel failed");
};
configureAutoEnabledChannels([broken, createChannelPlugin({ id: "healthy" })]);
mocks.buildChannelUiCatalog.mockImplementation((plugins: Array<{ id: string }>) => ({
order: plugins.map((plugin) => plugin.id),
labels: {},
detailLabels: {},
systemImages: {},
entries: {},
}));
const payload = await runChannelsStatus({ probe: false, timeoutMs: 1000 });
expect(payload.partial).toBe(true);
expect(payload.warnings).toEqual(["broken channel status failed: Error: channel failed"]);
expect(requireGatewayRecord(payload.channels, "channels payload").healthy).toEqual({
configured: true,
});
});
it("isolates a timed-out channel probe while another channel succeeds", async () => {
vi.useFakeTimers();
try {
+9 -1
View File
@@ -510,6 +510,10 @@ export const channelsHandlers: GatewayRequestHandlers = {
await buildAccountSnapshot(channelId, plugin, accountId, defaultAccountId),
),
limit: probe ? CHANNEL_STATUS_PROBE_CONCURRENCY : accountIds.length || 1,
onTaskError: (error, index) => {
const accountId = accountIds[index] ?? `account ${index + 1}`;
statusWarnings.push(`${channelId}:${accountId} status failed: ${formatForLog(error)}`);
},
});
const accounts: ChannelAccountSnapshot[] = [];
for (const result of results) {
@@ -572,6 +576,10 @@ export const channelsHandlers: GatewayRequestHandlers = {
return { pluginId: plugin.id, summary, accounts, defaultAccountId };
}),
limit: probe ? CHANNEL_STATUS_PROBE_CONCURRENCY : selectedPlugins.length || 1,
onTaskError: (error, index) => {
const channelId = statusPlugins[index]?.id ?? `channel ${index + 1}`;
statusWarnings.push(`${channelId} channel status failed: ${formatForLog(error)}`);
},
});
for (const result of channelResults) {
if (result) {
@@ -582,7 +590,7 @@ export const channelsHandlers: GatewayRequestHandlers = {
}
if (statusWarnings.length > 0) {
payload.partial = true;
payload.warnings = statusWarnings.slice(0, 50);
payload.warnings = statusWarnings.toSorted().slice(0, 50);
}
respond(true, payload, undefined);