mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix(health): activated plugin failures are silently omitted (#129161)
* fix(health): surface activated plugin and service failures * test(health): use complete deep-status snapshot fixture
This commit is contained in:
committed by
GitHub
parent
aa3e0ae429
commit
b7d5e4fb66
@@ -257,6 +257,54 @@ describe("formatHealthChannelLines", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("surfaces activated plugin failures without promoting inactive load errors", () => {
|
||||
const summary = createHealthSummary({ channels: {}, channelOrder: [], channelLabels: {} });
|
||||
summary.plugins = {
|
||||
loaded: ["calendar"],
|
||||
errors: [
|
||||
{
|
||||
id: "calendar",
|
||||
origin: "workspace",
|
||||
activated: true,
|
||||
failurePhase: "service",
|
||||
error: "service scheduler: address already in use",
|
||||
},
|
||||
{
|
||||
id: "inactive",
|
||||
origin: "workspace",
|
||||
activated: false,
|
||||
failurePhase: "load",
|
||||
error: "inactive plugin load failed",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(formatHealthChannelLines(summary)).toStrictEqual([
|
||||
"Plugin calendar: failed - service scheduler: address already in use; run openclaw doctor",
|
||||
]);
|
||||
});
|
||||
|
||||
it("bounds activated plugin failure details and summarizes omitted failures", () => {
|
||||
const summary = createHealthSummary({ channels: {}, channelOrder: [], channelLabels: {} });
|
||||
summary.plugins = {
|
||||
loaded: [],
|
||||
errors: Array.from({ length: 22 }, (_, index) => ({
|
||||
id: `plugin-${index}`,
|
||||
origin: "workspace",
|
||||
activated: true,
|
||||
error: "x".repeat(600),
|
||||
})),
|
||||
};
|
||||
|
||||
const lines = formatHealthChannelLines(summary);
|
||||
|
||||
expect(lines).toHaveLength(21);
|
||||
expect(lines[0]).toBe(`Plugin plugin-0: failed - ${"x".repeat(500)}; run openclaw doctor`);
|
||||
expect(lines.at(-1)).toBe(
|
||||
"Plugins: failed - 2 additional activated failures; run openclaw doctor",
|
||||
);
|
||||
});
|
||||
|
||||
it("formats iMessage probe failures as failed health lines", () => {
|
||||
const summary = createHealthSummary({
|
||||
channels: {
|
||||
|
||||
@@ -139,7 +139,7 @@ const isProbeFailure = (summary: ChannelAccountHealthSummary): boolean => {
|
||||
return ok === false;
|
||||
};
|
||||
|
||||
/** Formats one terse health line per channel, optionally including every account. */
|
||||
/** Formats terse channel and activated-plugin health lines for shared CLI surfaces. */
|
||||
export const formatHealthChannelLines = (
|
||||
summary: HealthSummary,
|
||||
opts: {
|
||||
@@ -269,5 +269,16 @@ export const formatHealthChannelLines = (
|
||||
: "unknown";
|
||||
lines.push(`${label}: ${passiveState}`);
|
||||
}
|
||||
const failedPlugins = (summary.plugins?.errors ?? []).filter((plugin) => plugin.activated);
|
||||
for (const plugin of failedPlugins.slice(0, 20)) {
|
||||
const id = sanitizeTerminalText(plugin.id).slice(0, 120);
|
||||
const error = sanitizeTerminalText(plugin.error).slice(0, 500);
|
||||
lines.push(`Plugin ${id}: failed - ${error}; run openclaw doctor`);
|
||||
}
|
||||
if (failedPlugins.length > 20) {
|
||||
lines.push(
|
||||
`Plugins: failed - ${failedPlugins.length - 20} additional activated failures; run openclaw doctor`,
|
||||
);
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
|
||||
@@ -170,7 +170,7 @@ describe("healthCommand", () => {
|
||||
probeGatewayStatusMock.mockReset();
|
||||
});
|
||||
|
||||
it("renders the gateway session path identically in JSON and text", async () => {
|
||||
it("preserves plugin health in JSON while surfacing activated failures in text", async () => {
|
||||
const agentSessions = {
|
||||
path: "/tmp/sessions.json",
|
||||
count: 1,
|
||||
@@ -194,6 +194,24 @@ describe("healthCommand", () => {
|
||||
},
|
||||
sessions: agentSessions,
|
||||
});
|
||||
snapshot.plugins = {
|
||||
loaded: ["calendar"],
|
||||
errors: [
|
||||
{
|
||||
id: "calendar",
|
||||
origin: "workspace",
|
||||
activated: true,
|
||||
failurePhase: "service",
|
||||
error: "service scheduler: address already in use",
|
||||
},
|
||||
{
|
||||
id: "inactive",
|
||||
origin: "workspace",
|
||||
activated: false,
|
||||
error: "inactive plugin load failed",
|
||||
},
|
||||
],
|
||||
};
|
||||
callGatewayMock.mockResolvedValueOnce(snapshot);
|
||||
|
||||
await healthCommand({ json: true, timeoutMs: 5000, config: {} }, runtime as never);
|
||||
@@ -204,6 +222,7 @@ describe("healthCommand", () => {
|
||||
expect(parsed.channels.whatsapp?.linked).toBe(true);
|
||||
expect(parsed.channels.telegram?.configured).toBe(true);
|
||||
expect(parsed.sessions.count).toBe(1);
|
||||
expect(parsed.plugins).toEqual(snapshot.plugins);
|
||||
|
||||
runtime.log.mockClear();
|
||||
callGatewayMock.mockResolvedValueOnce(snapshot);
|
||||
@@ -211,6 +230,10 @@ describe("healthCommand", () => {
|
||||
|
||||
const output = stripAnsi(runtime.log.mock.calls.map((call) => String(call[0])).join("\n"));
|
||||
expect(output).toContain(`Session store (main): ${parsed.sessions.path}`);
|
||||
expect(output).toContain(
|
||||
"Plugin calendar: failed - service scheduler: address already in use; run openclaw doctor",
|
||||
);
|
||||
expect(output).not.toContain("inactive plugin load failed");
|
||||
});
|
||||
|
||||
it("prints the gateway probe duration in text output", async () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Status command section tests cover footer, health, and report section rendering.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatHealthChannelLines } from "./health-format.js";
|
||||
import type { HealthSummary } from "./health.js";
|
||||
import {
|
||||
buildStatusFooterLines,
|
||||
@@ -291,6 +292,46 @@ describe("status.command-sections", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("marks activated plugin service failures as warnings in deep health rows", () => {
|
||||
const health: HealthSummary = {
|
||||
ok: true,
|
||||
ts: 0,
|
||||
durationMs: 42,
|
||||
heartbeatSeconds: 60,
|
||||
defaultAgentId: "main",
|
||||
agents: [],
|
||||
sessions: { path: "/tmp/sessions.json", count: 0, recent: [] },
|
||||
channels: {},
|
||||
channelOrder: [],
|
||||
channelLabels: {},
|
||||
plugins: {
|
||||
loaded: ["calendar"],
|
||||
errors: [
|
||||
{
|
||||
id: "calendar",
|
||||
origin: "workspace",
|
||||
activated: true,
|
||||
failurePhase: "service",
|
||||
error: "service scheduler: address already in use",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const rows = buildStatusHealthRows({
|
||||
health,
|
||||
formatHealthChannelLines,
|
||||
ok: (value) => `ok(${value})`,
|
||||
warn: (value) => `warn(${value})`,
|
||||
muted: (value) => `muted(${value})`,
|
||||
});
|
||||
|
||||
expect(rows).toContainEqual({
|
||||
Item: "Plugin calendar",
|
||||
Status: "warn(WARN)",
|
||||
Detail: "failed - service scheduler: address already in use; run openclaw doctor",
|
||||
});
|
||||
});
|
||||
|
||||
it("adds degraded event-loop health to status rows", () => {
|
||||
const rows = buildStatusHealthRows({
|
||||
health: {
|
||||
|
||||
Reference in New Issue
Block a user