fix: handle mixed Codex OAuth PI recovery

This commit is contained in:
Shakker
2026-05-06 20:34:58 +01:00
committed by Peter Steinberger
parent 7b53e58670
commit 7de45e0dfd
7 changed files with 345 additions and 26 deletions

View File

@@ -53,6 +53,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Telegram/Codex: generate DM topic labels with Codex-compatible simple-completion requests so auto-created private topics can be renamed instead of staying `New Chat`.
- Doctor/Codex OAuth: preserve working `openai-codex/*` PI routes during `doctor --fix`, recover 2026.5.5-rewritten `openai/*` GPT-5 routes when only Codex OAuth auth is available, and warn without rewriting mixed Codex OAuth plus direct OpenAI PI routes, so update repair does not break subscription-auth setups. Fixes #78407. Thanks @shakkernerd.
- Plugins/runtime fetch: drop third-party symbol metadata from plain request header dictionaries before passing them into native `fetch` or `Headers`, so SDK and guarded/proxy fetch paths do not reject otherwise valid plugin requests. Fixes #77846. Thanks @shakkernerd.
- Web fetch: bound guarded dispatcher cleanup after request timeouts so timed-out fetches return tool errors instead of leaving Gateway tool lanes active. (#78439) Thanks @obviyus.
- Mattermost/setup: prompt for and persist the server base URL after the bot token in `openclaw setup --wizard`, instead of failing validation before `--http-url` is collected. Fixes #76670. Thanks @jacobtomlinson.

View File

@@ -47,7 +47,7 @@ Notes:
- Doctor also scans `~/.openclaw/cron/jobs.json` (or `cron.store`) for legacy cron job shapes and can rewrite them in place before the scheduler has to auto-normalize them at runtime.
- On Linux, doctor warns when the user's crontab still runs legacy `~/.openclaw/bin/ensure-whatsapp.sh`; that script is no longer maintained and can log false WhatsApp gateway outages when cron lacks the systemd user-bus environment.
- When WhatsApp is enabled, doctor checks for a degraded Gateway event loop with local `openclaw-tui` clients still running. `doctor --fix` stops only verified local TUI clients so WhatsApp replies are not queued behind stale TUI refresh loops.
- Doctor checks `openai-codex/*` model refs across primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale session route pins. `--fix` rewrites them to `openai/*` only when the native Codex runtime is installed, enabled, contributes the `codex` harness, and has usable OAuth, or when direct OpenAI auth is already available and no usable Codex OAuth route would be moved. When `openai-codex/*` is the working Codex OAuth route through OpenClaw PI, doctor preserves it. If an earlier repair left `openai/*` GPT-5 routes on PI while only Codex OAuth auth is available, `--fix` recovers them back to `openai-codex/*`.
- Doctor checks `openai-codex/*` model refs across primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and stale session route pins. `--fix` rewrites them to `openai/*` only when the native Codex runtime is installed, enabled, contributes the `codex` harness, and has usable OAuth, or when direct OpenAI auth is already available and no usable Codex OAuth route would be moved. When `openai-codex/*` is the working Codex OAuth route through OpenClaw PI, doctor preserves it. If an earlier repair left `openai/*` GPT-5 routes on PI while only Codex OAuth auth is available, `--fix` recovers them back to `openai-codex/*`; when direct OpenAI auth is also usable, doctor warns and leaves the ambiguous mixed-auth route unchanged for explicit confirmation.
- Doctor cleans legacy plugin dependency staging state created by older OpenClaw versions. It also repairs missing downloadable plugins that are referenced by config, such as `plugins.entries`, configured channels, configured provider/search settings, or configured agent runtimes. During package updates, doctor skips package-manager plugin repair until the package swap is complete; rerun `openclaw doctor --fix` afterward if a configured plugin still needs recovery. If the download fails, doctor reports the install error and preserves the configured plugin entry for the next repair attempt.
- Doctor repairs stale plugin config by removing missing plugin ids from `plugins.allow`/`plugins.entries`, plus matching dangling channel config, heartbeat targets, and channel model overrides when plugin discovery is healthy.
- Doctor quarantines invalid plugin config by disabling the affected `plugins.entries.<id>` entry and removing its invalid `config` payload. Gateway startup already skips only that bad plugin so other plugins and channels can keep running.

View File

@@ -108,7 +108,7 @@ cat ~/.openclaw/openclaw.json
- Gateway runtime checks (service installed but not running; cached launchd label).
- Channel status warnings (probed from the running gateway).
- WhatsApp responsiveness checks for degraded Gateway event-loop health with local TUI clients still running; `--fix` stops only verified local TUI clients.
- Codex route repair for `openai-codex/*` model refs in primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and session route pins; `--fix` preserves working Codex OAuth PI routes, rewrites to `openai/*` only when the native Codex runtime or direct OpenAI auth path is usable, and recovers prior `openai/*` GPT-5 PI rewrites when only Codex OAuth auth is available.
- Codex route repair for `openai-codex/*` model refs in primary models, fallbacks, heartbeat/subagent/compaction overrides, hooks, channel model overrides, and session route pins; `--fix` preserves working Codex OAuth PI routes, rewrites to `openai/*` only when the native Codex runtime or direct OpenAI auth path is usable, recovers prior `openai/*` GPT-5 PI rewrites when only Codex OAuth auth is available, and warns without rewriting when both Codex OAuth and direct OpenAI auth make an already-rewritten PI route ambiguous.
- Supervisor config audit (launchd/systemd/schtasks) with optional repair.
- Embedded proxy environment cleanup for gateway services that captured shell `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` values during install or update.
- Gateway runtime best-practice checks (Node vs Bun, version-manager paths).
@@ -271,6 +271,7 @@ That stages grounded durable candidates into the short-term dreaming store while
- `openai-codex/*` becomes `openai/*` with `agentRuntime.id: "codex"` only when Codex is installed, enabled, contributes the `codex` harness, and has usable OAuth.
- `openai-codex/*` becomes `openai/*` on PI only when direct OpenAI auth is already usable and no working Codex OAuth route would be moved.
- Prior `openai/*` GPT-5 PI rewrites are recovered back to supported `openai-codex/*` refs when only Codex OAuth auth is available.
- Prior `openai/*` GPT-5 PI rewrites warn and stay unchanged when direct OpenAI auth is also usable, so users can confirm whether the direct OpenAI API route is intentional before switching back to Codex OAuth through PI.
- Existing model fallback lists are preserved when refs are rewritten or recovered; copied per-model settings move with the selected key.
- Persisted session `modelProvider`/`providerOverride`, `model`/`modelOverride`, fallback notices, auth-profile pins, and Codex harness pins are repaired across all discovered agent session stores when the route can be moved safely.
- `/codex ...` means "control or bind a native Codex conversation from chat."

View File

@@ -90,9 +90,9 @@ If your config uses `plugins.allow`, include `codex` there too:
Use `openai-codex/gpt-*` only when you intentionally want Codex OAuth through
OpenClaw PI. `openclaw doctor --fix` preserves that working subscription route,
rewrites it to `openai/gpt-*` only when the native Codex runtime or direct
OpenAI auth path is usable, and can recover prior `openai/gpt-*` PI rewrites
back to supported `openai-codex/gpt-*` refs when only Codex OAuth auth is
available.
OpenAI auth path is usable, can recover prior `openai/gpt-*` PI rewrites back
to supported `openai-codex/gpt-*` refs when only Codex OAuth auth is available,
and warns without rewriting when direct OpenAI auth makes the route ambiguous.
## What this plugin changes
@@ -227,6 +227,7 @@ choose one of these outcomes:
- rewrite to `openai/<model>` plus `agentRuntime.id: "codex"` when Codex is installed, enabled, contributes the `codex` harness, and has usable OAuth
- rewrite to direct `openai/<model>` on PI only when direct OpenAI auth is already usable and no working Codex OAuth route would be moved
- recover prior `openai/*` GPT-5 PI rewrites back to supported `openai-codex/*` refs when only Codex OAuth auth is available
- warn without rewriting when a prior `openai/*` GPT-5 PI rewrite has both Codex OAuth and direct OpenAI auth available
The `codex` route forces the native Codex harness. The `pi` route keeps the
agent on the default OpenClaw runner instead of enabling or installing Codex as

View File

@@ -287,7 +287,9 @@ Choose your preferred auth method and follow the setup steps.
direct `openai/*` API-key billing. If an earlier repair already rewrote a
PI Codex OAuth setup to `openai/*` and no direct OpenAI auth is available,
rerun `openclaw doctor --fix` to recover the route back to
`openai-codex/*`.
`openai-codex/*`. If direct OpenAI auth is also available, doctor warns and
leaves the mixed-auth route unchanged until you confirm whether direct
OpenAI API auth or Codex OAuth through PI is intended.
### Context window cap

View File

@@ -1,3 +1,6 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { SessionEntry } from "../../../config/sessions/types.js";
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
@@ -333,6 +336,281 @@ describe("collectCodexRouteWarnings", () => {
expect(result.changes.join("\n")).toContain("Recovered Codex OAuth model routes");
});
it("warns but does not recover mixed OpenAI PI routes when direct OpenAI auth is also usable", () => {
const store = {
profiles: {
"openai-codex:default": {
type: "oauth",
provider: "openai-codex",
access: "access-token",
},
"openai:default": {
type: "api-key",
provider: "openai",
apiKey: "sk-test",
},
},
usageStats: {},
};
mocks.ensureAuthProfileStore.mockReturnValue(store);
mocks.resolveAuthProfileOrder.mockImplementation(({ provider }) =>
provider === "openai-codex"
? ["openai-codex:default"]
: provider === "openai"
? ["openai:default"]
: [],
);
const cfg = {
agents: {
defaults: {
model: "openai/gpt-5.5",
agentRuntime: { id: "pi" },
},
},
} as OpenClawConfig;
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-codex-route-"));
try {
const env = { OPENCLAW_CONFIG_PATH: path.join(tempRoot, "openclaw.json") };
const warnings = collectCodexRouteWarnings({ cfg, env });
expect(warnings.join("\n")).toContain(
"Direct `openai/*` GPT-5 model refs are configured for PI while Codex OAuth auth is also available.",
);
expect(warnings.join("\n")).toContain("agents.defaults.model: openai/gpt-5.5");
expect(warnings.join("\n")).toContain("leaves these mixed-auth routes unchanged");
const result = maybeRepairCodexRoutes({
cfg,
env,
shouldRepair: true,
});
expect(result.cfg.agents?.defaults?.model).toBe("openai/gpt-5.5");
expect(result.cfg.agents?.defaults?.agentRuntime).toEqual({ id: "pi" });
expect(result.changes).toEqual([]);
expect(result.warnings.join("\n")).toContain("leaves these mixed-auth routes unchanged");
} finally {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
it("does not recover mixed OpenAI PI routes from config backup alone", () => {
const store = {
profiles: {
"openai-codex:default": {
type: "oauth",
provider: "openai-codex",
access: "access-token",
},
"openai:default": {
type: "api-key",
provider: "openai",
apiKey: "sk-test",
},
},
usageStats: {},
};
mocks.ensureAuthProfileStore.mockReturnValue(store);
mocks.resolveAuthProfileOrder.mockImplementation(({ provider }) =>
provider === "openai-codex"
? ["openai-codex:default"]
: provider === "openai"
? ["openai:default"]
: [],
);
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-codex-route-"));
try {
const configPath = path.join(tempRoot, "openclaw.json");
const env = { OPENCLAW_CONFIG_PATH: configPath };
fs.writeFileSync(
`${configPath}.bak`,
JSON.stringify({
agents: {
defaults: {
model: {
primary: "openai-codex/gpt-5.5",
fallbacks: ["openai-codex/gpt-5.4"],
},
agentRuntime: { id: "pi" },
models: {
"openai-codex/gpt-5.5": { source: "backup" },
"openai-codex/gpt-5.4": { source: "fallback" },
},
},
list: [
{
id: "main",
model: "openai-codex/gpt-5.5",
agentRuntime: { id: "pi" },
},
],
},
}),
);
const cfg = {
agents: {
defaults: {
model: {
primary: "openai/gpt-5.5",
fallbacks: ["openai/gpt-5.4"],
},
agentRuntime: { id: "pi" },
models: {
"openai/gpt-5.5": { source: "current" },
"openai/gpt-5.4": { source: "current-fallback" },
},
},
list: [
{
id: "main",
model: "openai/gpt-5.5",
agentRuntime: { id: "pi" },
},
],
},
} as OpenClawConfig;
const warnings = collectCodexRouteWarnings({ cfg, env });
expect(warnings.join("\n")).toContain("leaves these mixed-auth routes unchanged");
expect(warnings.join("\n")).not.toContain("config backup");
const result = maybeRepairCodexRoutes({
cfg,
env,
shouldRepair: true,
});
expect(result.warnings.join("\n")).toContain("leaves these mixed-auth routes unchanged");
expect(result.changes).toEqual([]);
expect(result.cfg.agents?.defaults?.model).toEqual({
primary: "openai/gpt-5.5",
fallbacks: ["openai/gpt-5.4"],
});
expect(result.cfg.agents?.defaults?.models).toEqual({
"openai/gpt-5.5": { source: "current" },
"openai/gpt-5.4": { source: "current-fallback" },
});
expect(result.cfg.agents?.list?.[0]).toMatchObject({
id: "main",
model: "openai/gpt-5.5",
});
} finally {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
it("warns for mixed OpenAI PI routes even when the native Codex runtime is ready", () => {
const store = {
profiles: {
"openai-codex:default": {
type: "oauth",
provider: "openai-codex",
access: "access-token",
},
"openai:default": {
type: "api-key",
provider: "openai",
apiKey: "sk-test",
},
},
usageStats: {},
};
const index = {
plugins: [
{
pluginId: "codex",
enabled: true,
startup: {
agentHarnesses: ["codex"],
},
},
],
};
mocks.ensureAuthProfileStore.mockReturnValue(store);
mocks.loadInstalledPluginIndex.mockReturnValue(index);
mocks.getInstalledPluginRecord.mockReturnValue(index.plugins[0]);
mocks.isInstalledPluginEnabled.mockReturnValue(true);
mocks.resolveAuthProfileOrder.mockImplementation(({ provider }) =>
provider === "openai-codex"
? ["openai-codex:default"]
: provider === "openai"
? ["openai:default"]
: [],
);
const warnings = collectCodexRouteWarnings({
cfg: {
plugins: {
entries: {
codex: {
enabled: true,
},
},
},
agents: {
defaults: {
agentRuntime: { id: "codex" },
},
list: [
{
id: "pi-worker",
model: "openai/gpt-5.5",
agentRuntime: { id: "pi" },
},
],
},
} as OpenClawConfig,
});
expect(warnings.join("\n")).toContain("agents.list.pi-worker.model: openai/gpt-5.5");
expect(warnings.join("\n")).toContain("leaves these mixed-auth routes unchanged");
});
it("preserves per-agent PI runtime overrides when recovering under non-PI defaults", () => {
const store = {
profiles: {
"openai-codex:default": {
type: "oauth",
provider: "openai-codex",
access: "access-token",
},
},
usageStats: {},
};
mocks.ensureAuthProfileStore.mockReturnValue(store);
mocks.resolveAuthProfileOrder.mockImplementation(({ provider }) =>
provider === "openai-codex" ? ["openai-codex:default"] : [],
);
const result = maybeRepairCodexRoutes({
cfg: {
agents: {
defaults: {
agentRuntime: { id: "codex" },
},
list: [
{
id: "pi-worker",
model: "openai/gpt-5.5",
agentRuntime: { id: "pi" },
},
],
},
} as OpenClawConfig,
shouldRepair: true,
});
expect(result.warnings).toEqual([]);
expect(result.cfg.agents?.list?.[0]).toMatchObject({
id: "pi-worker",
model: "openai-codex/gpt-5.5",
agentRuntime: { id: "pi" },
});
expect(result.changes.join("\n")).toContain("Recovered Codex OAuth model routes");
});
it("does not recover suppressed old Codex OAuth model refs", () => {
const store = {
profiles: {

View File

@@ -31,6 +31,7 @@ type CodexRouteRepairPlan = {
runtime: CodexRepairRuntime;
rewriteCodexRoutes: boolean;
recoverBrokenPiRoutes: boolean;
warnBrokenPiRoutes: boolean;
hasUsableCodexOAuth: boolean;
hasUsableOpenAIAuth: boolean;
};
@@ -727,8 +728,9 @@ function rewriteModelConfigSlotToCodex(params: {
if (!codexModel) {
return entry;
}
const path = `${params.path}.fallbacks.${index}`;
params.hits.push({
path: `${params.path}.fallbacks.${index}`,
path,
model,
canonicalModel: codexModel,
});
@@ -774,8 +776,9 @@ function rewriteModelsMapToCodex(params: {
if (!codexModel) {
continue;
}
const path = `${params.path}.${openAIRef}`;
params.hits.push({
path: `${params.path}.${openAIRef}`,
path,
model: openAIRef,
canonicalModel: codexModel,
});
@@ -784,11 +787,14 @@ function rewriteModelsMapToCodex(params: {
}
}
function clearPiRuntimeOverride(agent: MutableRecord): void {
function clearPiRuntimeOverride(agent: MutableRecord, inheritedRuntime: string | undefined): void {
const agentRuntime = asMutableRecord(agent.agentRuntime);
if (!agentRuntime || normalizeString(agentRuntime.id) !== "pi") {
return;
}
if (inheritedRuntime !== undefined && inheritedRuntime !== "pi") {
return;
}
delete agentRuntime.id;
if (Object.keys(agentRuntime).length === 0) {
delete agent.agentRuntime;
@@ -860,6 +866,7 @@ function rewriteAgentModelRefsToCodex(params: {
agent: MutableRecord | undefined;
path: string;
currentRuntime: string;
inheritedRuntime?: string;
rewriteModelsMap?: boolean;
}): void {
if (!params.agent || params.currentRuntime !== "pi") {
@@ -875,7 +882,7 @@ function rewriteAgentModelRefsToCodex(params: {
clearsRuntimeOnPrimary: key === "model",
});
if (key === "model" && recoveredPrimary) {
clearPiRuntimeOverride(params.agent);
clearPiRuntimeOverride(params.agent, params.inheritedRuntime);
}
}
rewriteStringModelSlotToCodex({
@@ -1009,10 +1016,12 @@ function rewriteConfigModelRefsToCodex(params: { cfg: OpenClawConfig; env?: Node
agent: asMutableRecord(nextConfig.agents?.defaults),
path: "agents.defaults",
currentRuntime: resolveRuntime({ env: params.env, defaultsRuntime }),
inheritedRuntime: undefined,
rewriteModelsMap: true,
});
for (const [index, agent] of (nextConfig.agents?.list ?? []).entries()) {
const id = typeof agent.id === "string" && agent.id.trim() ? agent.id.trim() : String(index);
const inheritedRuntime = resolveRuntime({ env: params.env, defaultsRuntime });
rewriteAgentModelRefsToCodex({
hits,
agent: agent as MutableRecord,
@@ -1022,6 +1031,7 @@ function rewriteConfigModelRefsToCodex(params: { cfg: OpenClawConfig; env?: Node
agentRuntime: agent.agentRuntime,
defaultsRuntime,
}),
inheritedRuntime,
});
}
const channelsModelByChannel = asMutableRecord(nextConfig.channels?.modelByChannel);
@@ -1164,7 +1174,8 @@ function resolveCodexRouteRepairPlan(params: {
return {
runtime,
rewriteCodexRoutes,
recoverBrokenPiRoutes: runtime === "pi" && hasUsableCodexOAuth && !openAIAuthAvailable,
recoverBrokenPiRoutes: hasUsableCodexOAuth && !openAIAuthAvailable,
warnBrokenPiRoutes: hasUsableCodexOAuth,
hasUsableCodexOAuth,
hasUsableOpenAIAuth: openAIAuthAvailable,
};
@@ -1174,7 +1185,7 @@ function resolveSessionRouteRepairMode(plan: CodexRouteRepairPlan): CodexSession
if (plan.rewriteCodexRoutes) {
return "rewrite-to-openai";
}
return plan.recoverBrokenPiRoutes ? "recover-codex-oauth" : "preserve";
return plan.runtime === "pi" && plan.recoverBrokenPiRoutes ? "recover-codex-oauth" : "preserve";
}
function formatCodexRouteChange(hit: CodexRouteHit, runtime: CodexRepairRuntime): string {
@@ -1186,6 +1197,31 @@ function formatCodexRouteRecovery(hit: CodexRouteHit): string {
return `${hit.path}: ${hit.model} -> ${hit.canonicalModel}.`;
}
function formatBrokenPiRouteWarning(hits: CodexRouteHit[], plan: CodexRouteRepairPlan): string {
const lines = plan.recoverBrokenPiRoutes
? [
"- Direct `openai/*` GPT-5 model refs are configured for PI, but only Codex OAuth auth is available.",
...hits.map(
(hit) =>
`- ${hit.path}: ${hit.model} can be recovered to ${hit.canonicalModel}${
hit.runtime ? `; current runtime is "${hit.runtime}"` : ""
}.`,
),
"- Run `openclaw doctor --fix` to recover the Codex OAuth PI route instead of leaving the agent on a direct OpenAI API route without OpenAI API auth.",
]
: [
"- Direct `openai/*` GPT-5 model refs are configured for PI while Codex OAuth auth is also available.",
...hits.map(
(hit) =>
`- ${hit.path}: ${hit.model} can use direct OpenAI auth or ${hit.canonicalModel} for Codex OAuth through PI${
hit.runtime ? `; current runtime is "${hit.runtime}"` : ""
}.`,
),
"- `openclaw doctor --fix` leaves these mixed-auth routes unchanged because direct OpenAI auth is usable; confirm the direct OpenAI API route is intentional or switch back to `openai-codex/*` for Codex OAuth through PI.",
];
return lines.join("\n");
}
export function collectCodexRouteWarnings(params: {
cfg: OpenClawConfig;
env?: NodeJS.ProcessEnv;
@@ -1195,7 +1231,7 @@ export function collectCodexRouteWarnings(params: {
cfg: params.cfg,
env: params.env,
});
const recoverableHits = plan.recoverBrokenPiRoutes
const recoverableHits = plan.warnBrokenPiRoutes
? collectRecoverableOpenAIModelRefs(params.cfg, params.env)
: [];
if (hits.length === 0 && recoverableHits.length === 0) {
@@ -1222,18 +1258,11 @@ export function collectCodexRouteWarnings(params: {
);
}
if (recoverableHits.length > 0) {
warnings.push(
[
"- Direct `openai/*` GPT-5 model refs are configured for PI, but only Codex OAuth auth is available.",
...recoverableHits.map(
(hit) =>
`- ${hit.path}: ${hit.model} can be recovered to ${hit.canonicalModel}${
hit.runtime ? `; current runtime is "${hit.runtime}"` : ""
}.`,
),
"- Run `openclaw doctor --fix` to recover the Codex OAuth PI route instead of leaving the agent on a direct OpenAI API route without OpenAI API auth.",
].join("\n"),
);
if (plan.recoverBrokenPiRoutes) {
warnings.push(formatBrokenPiRouteWarning(recoverableHits, plan));
} else {
warnings.push(formatBrokenPiRouteWarning(recoverableHits, plan));
}
}
return warnings;
}
@@ -1253,7 +1282,11 @@ export function maybeRepairCodexRoutes(params: {
const recoverableHits = plan.recoverBrokenPiRoutes
? collectRecoverableOpenAIModelRefs(params.cfg, params.env)
: [];
if (hits.length === 0 && recoverableHits.length === 0) {
const mixedAuthHits =
!plan.recoverBrokenPiRoutes && plan.warnBrokenPiRoutes
? collectRecoverableOpenAIModelRefs(params.cfg, params.env)
: [];
if (hits.length === 0 && recoverableHits.length === 0 && mixedAuthHits.length === 0) {
return { cfg: params.cfg, warnings: [], changes: [] };
}
if (!params.shouldRepair) {
@@ -1301,6 +1334,9 @@ export function maybeRepairCodexRoutes(params: {
);
}
}
if (mixedAuthHits.length > 0) {
warnings.push(formatBrokenPiRouteWarning(mixedAuthHits, plan));
}
return {
cfg,
warnings,