fix: surface disabled codex plugin routes in doctor lint

This commit is contained in:
brokemac79
2026-05-31 20:49:27 +01:00
committed by Josh Lehman
parent 7c15c2765e
commit 41bcde2d7d
4 changed files with 141 additions and 0 deletions

View File

@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resetCoreHealthChecksForTest } from "../flows/doctor-core-checks.js";
import { clearHealthChecksForTest } from "../flows/health-check-registry.js";
import { runDoctorLintCli } from "./doctor-lint.js";
@@ -141,6 +142,55 @@ describe("runDoctorLintCli", () => {
}
});
it("reports disabled Codex plugin routes through doctor lint", async () => {
mocks.readConfigFileSnapshot.mockResolvedValue({
exists: true,
valid: true,
config: {
plugins: {
entries: {
codex: { enabled: false },
},
},
agents: {
defaults: {
model: {
primary: "gpt-5.5",
},
},
},
} as unknown as OpenClawConfig,
path: "/tmp/openclaw.json",
});
const stdout = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
try {
const exitCode = await runDoctorLintCli(runtime, {
json: true,
onlyIds: ["core/doctor/codex-session-routes"],
});
expect(exitCode).toBe(1);
const payload = JSON.parse(String(stdout.mock.calls.at(-1)?.[0]));
expect(payload).toMatchObject({
ok: false,
checksRun: 1,
findings: [
{
checkId: "core/doctor/codex-session-routes",
severity: "warning",
path: "agents.defaults.model.primary",
target: "openai/gpt-5.5",
},
],
});
expect(payload.findings[0].message).toContain("Codex plugin is disabled by config");
expect(payload.findings[0].fixHint).toContain("openclaw doctor --fix");
} finally {
stdout.mockRestore();
}
});
it("rejects invalid severity thresholds", async () => {
await expect(runDoctorLintCli(runtime, { severityMin: "warnng" })).rejects.toThrow(
"Invalid --severity-min value",

View File

@@ -43,6 +43,12 @@ type DisabledCodexPluginRouteHit = {
modelRef: string;
canonicalModel: string;
};
export type DisabledCodexPluginRouteIssue = {
path: string;
modelRef: string;
canonicalModel: string;
blockedOutsideEntry: boolean;
};
type SharedDefaultCompactionOverrideConsumers = Record<CompactionOverrideKey, boolean>;
type MutableRecord = Record<string, unknown>;
@@ -1173,6 +1179,18 @@ function collectDisabledCodexPluginRouteHits(cfg: OpenClawConfig): DisabledCodex
return hits;
}
export function collectDisabledCodexPluginRouteIssues(
cfg: OpenClawConfig,
): DisabledCodexPluginRouteIssue[] {
const blockedOutsideEntry = codexPluginIsBlockedOutsideEntry(cfg);
return collectDisabledCodexPluginRouteHits(cfg).map((hit) => ({
path: hit.path,
modelRef: hit.modelRef,
canonicalModel: hit.canonicalModel,
blockedOutsideEntry,
}));
}
function enableCodexPluginForRequiredRoutes(params: {
cfg: OpenClawConfig;
routeHits: DisabledCodexPluginRouteHit[];

View File

@@ -327,6 +327,45 @@ describe("registerCoreHealthChecks", () => {
);
});
it("reports disabled Codex plugin routes as core health findings", async () => {
const check = getCheck(
createCoreHealthChecks(createDeps()),
"core/doctor/codex-session-routes",
);
const findings = await check.detect({
mode: "lint",
runtime,
cfg: {
plugins: {
entries: {
codex: { enabled: false },
},
},
agents: {
defaults: {
model: {
primary: "gpt-5.5",
},
},
},
} as unknown as OpenClawConfig,
});
expect(findings).toStrictEqual([
expect.objectContaining({
checkId: "core/doctor/codex-session-routes",
severity: "warning",
path: "agents.defaults.model.primary",
target: "openai/gpt-5.5",
requirement: "Codex plugin enabled for routes that use the Codex runtime.",
fixHint:
"Run `openclaw doctor --fix`: it enables plugins.entries.codex, or set the affected OpenAI models to an OpenClaw runtime policy.",
}),
]);
expect(findings[0]?.message).toContain("Codex plugin is disabled by config");
});
it("uses the read-only model catalog for hooks.gmail.model checks", async () => {
const cfg: OpenClawConfig = {
hooks: {

View File

@@ -18,6 +18,7 @@ import {
uiProtocolFreshnessIssueToHealthFinding,
uiProtocolFreshnessIssueToRepairEffects,
} from "../commands/doctor-ui.js";
import { collectDisabledCodexPluginRouteIssues } from "../commands/doctor/shared/codex-route-warnings.js";
import type { ConfigValidationIssue, OpenClawConfig } from "../config/types.openclaw.js";
import { resolveSecretInputRef, type SecretRef } from "../config/types.secrets.js";
import { hasAmbiguousGatewayAuthModeConfig } from "../gateway/auth-mode-policy.js";
@@ -29,6 +30,7 @@ import { registerHealthCheck } from "./health-check-registry.js";
import type { HealthCheck, HealthCheckContext, HealthFinding } from "./health-checks.js";
const BROWSER_CLAWD_PROFILE_RESIDUE_CHECK_ID = "core/doctor/browser-clawd-profile-residue";
const CODEX_SESSION_ROUTES_CHECK_ID = "core/doctor/codex-session-routes";
const FINAL_CONFIG_VALIDATION_CHECK_ID = "core/doctor/final-config-validation";
const loadDoctorCoreChecksRuntimeModule = async () =>
@@ -616,6 +618,37 @@ const legacyWhatsAppCrontabCheck: HealthCheck = {
},
};
const codexSessionRoutesCheck: HealthCheck = {
id: CODEX_SESSION_ROUTES_CHECK_ID,
kind: "core",
description: "Codex runtime routes have a registered Codex plugin harness before sessions run.",
source: "doctor",
async detect(ctx) {
return collectDisabledCodexPluginRouteIssues(ctx.cfg).map(
(issue): HealthFinding => ({
checkId: CODEX_SESSION_ROUTES_CHECK_ID,
severity: "warning",
message: [
`${issue.path} routes ${issue.modelRef} to ${issue.canonicalModel}`,
"with Codex runtime, but the Codex plugin is disabled by config.",
].join(" "),
path: issue.path,
target: issue.canonicalModel,
requirement: "Codex plugin enabled for routes that use the Codex runtime.",
fixHint: issue.blockedOutsideEntry
? [
"Enable plugin loading and remove codex from plugins.deny,",
"or set the affected OpenAI models to an OpenClaw runtime policy.",
].join(" ")
: [
"Run `openclaw doctor --fix`: it enables plugins.entries.codex,",
"or set the affected OpenAI models to an OpenClaw runtime policy.",
].join(" "),
}),
);
},
};
const gatewayPlatformNotesCheck: HealthCheck = {
id: "core/doctor/gateway-services/platform-notes",
kind: "core",
@@ -913,6 +946,7 @@ function createConvertedWorkflowChecks(deps: CoreHealthCheckDeps): readonly Heal
gatewayAuthCheck,
legacyStateCheck,
legacyWhatsAppCrontabCheck,
codexSessionRoutesCheck,
shellCompletionCheck,
uiProtocolFreshnessCheck,
gatewayPlatformNotesCheck,