Skip to content

Commit 0d9ac32

Browse files
committed
fix(doctor): wire process.env into plugin drift check and add regression test
The previous version passed ctx.env to noteWorkspaceStatus, but the top-level doctor flow never sets env on the context, so drift detection was unreachable in normal runs. - Use ctx.env ?? process.env so drift detection works for normal runs - Add regression test that seeds drifted install records and asserts doctor emits the Plugin version drift note
1 parent 5e7b621 commit 0d9ac32

2 files changed

Lines changed: 54 additions & 2 deletions

File tree

src/commands/doctor-workspace-status.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ const mocks = vi.hoisted(() => ({
1616
buildPluginCompatibilityWarnings: vi.fn(),
1717
listTaskFlowRecords: vi.fn<() => unknown[]>(() => []),
1818
listTasksForFlowId: vi.fn<(flowId: string) => unknown[]>((_flowId: string) => []),
19+
loadInstalledPluginIndexInstallRecords: vi.fn<() => Promise<Record<string, unknown>>>(() =>
20+
Promise.resolve({}),
21+
),
22+
VERSION: "2026.6.1",
1923
}));
2024

2125
vi.mock("../agents/agent-scope.js", () => ({
@@ -34,6 +38,15 @@ vi.mock("../plugins/status.js", () => ({
3438
mocks.buildPluginCompatibilityWarnings(...args),
3539
}));
3640

41+
vi.mock("../plugins/installed-plugin-index-records.js", () => ({
42+
loadInstalledPluginIndexInstallRecords: (...args: unknown[]) =>
43+
mocks.loadInstalledPluginIndexInstallRecords(...args),
44+
}));
45+
46+
vi.mock("../version.js", () => ({
47+
VERSION: mocks.VERSION,
48+
}));
49+
3750
vi.mock("../tasks/task-flow-runtime-internal.js", () => ({
3851
listTaskFlowRecords: () => mocks.listTaskFlowRecords(),
3952
}));
@@ -48,6 +61,7 @@ async function runNoteWorkspaceStatusForTest(
4861
opts?: {
4962
flows?: unknown[];
5063
tasksByFlowId?: (flowId: string) => unknown[];
64+
env?: Record<string, string>;
5165
},
5266
) {
5367
mocks.resolveDefaultAgentId.mockReturnValue("default");
@@ -66,7 +80,7 @@ async function runNoteWorkspaceStatusForTest(
6680
);
6781

6882
const noteSpy = vi.spyOn(noteModule, "note").mockImplementation(() => {});
69-
await noteWorkspaceStatus({});
83+
await noteWorkspaceStatus({}, opts?.env ? { env: opts.env as NodeJS.ProcessEnv } : undefined);
7084
return noteSpy;
7185
}
7286

@@ -236,4 +250,42 @@ describe("noteWorkspaceStatus", () => {
236250
noteSpy.mockRestore();
237251
}
238252
});
253+
254+
it("reports plugin version drift when an official plugin version mismatches the gateway", async () => {
255+
mocks.loadInstalledPluginIndexInstallRecords.mockResolvedValue({
256+
codex: {
257+
source: "npm",
258+
spec: "@openclaw/codex@latest",
259+
resolvedName: "@openclaw/codex",
260+
resolvedVersion: "2026.5.30-beta.1",
261+
},
262+
});
263+
264+
const noteSpy = await runNoteWorkspaceStatusForTest(
265+
createPluginLoadResult({
266+
plugins: [
267+
createPluginRecord({
268+
id: "codex",
269+
name: "Codex",
270+
status: "loaded",
271+
format: "bundle",
272+
bundleCapabilities: ["provider:codex"],
273+
}),
274+
],
275+
}),
276+
[],
277+
{ env: { OPENCLAW_HOME: "/tmp/test-openclaw" } },
278+
);
279+
try {
280+
const driftCalls = noteSpy.mock.calls.filter(([, title]) => title === "Plugin version drift");
281+
expect(driftCalls).toHaveLength(1);
282+
const [[body]] = driftCalls;
283+
expect(body).toContain("not on gateway 2026.6.1");
284+
expect(body).toContain("codex: 2026.5.30-beta.1 -> expected 2026.6.1");
285+
expect(body).toContain("openclaw plugins update <plugin-id>");
286+
} finally {
287+
noteSpy.mockRestore();
288+
mocks.loadInstalledPluginIndexInstallRecords.mockResolvedValue({});
289+
}
290+
});
239291
});

src/flows/doctor-health-contributions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ async function runSystemdLingerHealth(ctx: DoctorHealthFlowContext): Promise<voi
728728

729729
async function runWorkspaceStatusHealth(ctx: DoctorHealthFlowContext): Promise<void> {
730730
const { noteWorkspaceStatus } = await import("../commands/doctor-workspace-status.js");
731-
await noteWorkspaceStatus(ctx.cfg, { env: ctx.env });
731+
await noteWorkspaceStatus(ctx.cfg, { env: ctx.env ?? process.env });
732732
}
733733

734734
async function runSkillsHealth(ctx: DoctorHealthFlowContext): Promise<void> {

0 commit comments

Comments
 (0)