mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-30 21:46:44 +02:00
fix: quarantine invalid plugin configs
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Local models: default custom providers with only `baseUrl` to the Chat Completions adapter and trust loopback model requests automatically, so local OpenAI-compatible proxies receive `/v1/chat/completions` without timing out. Fixes #40024. Thanks @parachuteshe.
|
||||
- Agents/tools: scope tool-loop detection history to the active run when available, so scheduled heartbeat cycles no longer inherit stale repeated-call counts from previous runs. Fixes #40144. Thanks @mattbrown319.
|
||||
- Control UI: show loading, reload, and retry states when a lazy dashboard panel cannot load after an upgrade, so the Logs tab no longer appears blank on stale browser bundles. Fixes #72450. Thanks @sobergou.
|
||||
- Gateway/plugins: start the Gateway in degraded mode when a single plugin entry has invalid schema config, and let `openclaw doctor --fix` quarantine that plugin config instead of crash-looping every channel. Fixes #62976 and #70371. Thanks @Doraemon-Claw and @pksidekyk.
|
||||
- Agents/reasoning: recover fully wrapped unclosed `<think>` replies that would otherwise sanitize to empty text while keeping strict stripping for closed reasoning blocks and unclosed tails after visible text. Fixes #37696; supersedes #51915. Thanks @druide67 and @okuyam2y.
|
||||
- Control UI/Gateway: bind WebChat handshakes to their active socket and reject post-close server registrations, so aborted connects no longer leave zombie clients or misleading duplicate WebSocket connection logs. Fixes #72753. Thanks @LumenFromTheFuture.
|
||||
- Agents/fallback: split ambiguous provider failures into `empty_response`, `no_error_details`, and `unclassified`, and add flat fallback-step fields to structured fallback logs so primary-model failures stay visible when later fallbacks also fail. Fixes #71922; refs #71744. Thanks @andyk-ms and @nikolaykazakovvs-ux.
|
||||
|
||||
@@ -45,6 +45,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.
|
||||
- Doctor repairs missing bundled plugin runtime dependencies without writing into packaged global installs. For root-owned npm installs or hardened systemd units, set `OPENCLAW_PLUGIN_STAGE_DIR` to a writable directory such as `/var/lib/openclaw/plugin-runtime-deps`; it can also be a path-list such as `/opt/openclaw/plugin-runtime-deps:/var/lib/openclaw/plugin-runtime-deps`, where earlier roots are read-only lookup layers and the final root is the repair target.
|
||||
- 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.
|
||||
- Set `OPENCLAW_SERVICE_REPAIR_POLICY=external` when another supervisor owns the gateway lifecycle. Doctor still reports gateway/service health and applies non-service repairs, but skips service install/start/restart/bootstrap and legacy service cleanup.
|
||||
- Doctor auto-migrates legacy flat Talk config (`talk.voiceId`, `talk.modelId`, and friends) into `talk.provider` + `talk.providers.<provider>`.
|
||||
- Repeat `doctor --fix` runs no longer report/apply Talk normalization when the only difference is object key order.
|
||||
|
||||
@@ -79,7 +79,7 @@ Bare package names are checked against ClawHub first, then npm. Treat plugin ins
|
||||
<Accordion title="Config includes and invalid-config recovery">
|
||||
If your `plugins` section is backed by a single-file `$include`, `plugins install/update/enable/disable/uninstall` write through to that included file and leave `openclaw.json` untouched. Root includes, include arrays, and includes with sibling overrides fail closed instead of flattening. See [Config includes](/gateway/configuration) for the supported shapes.
|
||||
|
||||
If config is invalid, `plugins install` normally fails closed and tells you to run `openclaw doctor --fix` first. The only documented exception is a narrow bundled-plugin recovery path for plugins that explicitly opt into `openclaw.install.allowInvalidConfigRecovery`.
|
||||
If config is invalid during install, `plugins install` normally fails closed and tells you to run `openclaw doctor --fix` first. During Gateway startup, invalid config for one plugin is isolated to that plugin so other channels and plugins can keep running; `openclaw doctor --fix` can quarantine the invalid plugin entry. The only documented install-time exception is a narrow bundled-plugin recovery path for plugins that explicitly opt into `openclaw.install.allowInvalidConfigRecovery`.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="--force and reinstall vs update">
|
||||
|
||||
@@ -61,6 +61,11 @@ If config is invalid, install normally fails closed and points you at
|
||||
`openclaw doctor --fix`. The only recovery exception is a narrow bundled-plugin
|
||||
reinstall path for plugins that opt into
|
||||
`openclaw.install.allowInvalidConfigRecovery`.
|
||||
During Gateway startup, invalid config for one plugin is isolated to that plugin:
|
||||
startup logs the `plugins.entries.<id>.config` issue, skips that plugin during
|
||||
load, and keeps other plugins and channels online. Run `openclaw doctor --fix`
|
||||
to quarantine the bad plugin config by disabling that plugin entry and removing
|
||||
its invalid config payload; the normal config backup keeps the previous values.
|
||||
When a channel config references a plugin that is no longer discoverable but the
|
||||
same stale plugin id remains in plugin config or install records, Gateway startup
|
||||
logs warnings and skips that channel instead of blocking every other channel.
|
||||
@@ -203,7 +208,7 @@ or use `openclaw gateway restart` against the running Gateway.
|
||||
<Accordion title="Plugin states: disabled vs missing vs invalid">
|
||||
- **Disabled**: plugin exists but enablement rules turned it off. Config is preserved.
|
||||
- **Missing**: config references a plugin id that discovery did not find.
|
||||
- **Invalid**: plugin exists but its config does not match the declared schema.
|
||||
- **Invalid**: plugin exists but its config does not match the declared schema. Gateway startup skips only that plugin; `openclaw doctor --fix` can quarantine the invalid entry by disabling it and removing its config payload.
|
||||
</Accordion>
|
||||
|
||||
## Discovery and precedence
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "./shared/config-mutation-state.js";
|
||||
import { scanEmptyAllowlistPolicyWarnings } from "./shared/empty-allowlist-scan.js";
|
||||
import { maybeRepairExecSafeBinProfiles } from "./shared/exec-safe-bins.js";
|
||||
import { maybeRepairInvalidPluginConfig } from "./shared/invalid-plugin-config.js";
|
||||
import { maybeRepairLegacyToolsBySenderKeys } from "./shared/legacy-tools-by-sender.js";
|
||||
import { maybeRepairOpenPolicyAllowFrom } from "./shared/open-policy-allowfrom.js";
|
||||
import { maybeRepairStalePluginConfig } from "./shared/stale-plugin-config.js";
|
||||
@@ -58,6 +59,7 @@ export async function runDoctorRepairSequence(params: {
|
||||
applyMutation(maybeRepairOpenPolicyAllowFrom(state.candidate));
|
||||
applyMutation(maybeRepairBundledPluginLoadPaths(state.candidate, env));
|
||||
applyMutation(maybeRepairStalePluginConfig(state.candidate, env));
|
||||
applyMutation(maybeRepairInvalidPluginConfig(state.candidate));
|
||||
applyMutation(await maybeRepairAllowlistPolicyAllowFrom(state.candidate));
|
||||
|
||||
const emptyAllowlistWarnings = scanEmptyAllowlistPolicyWarnings(state.candidate, {
|
||||
|
||||
148
src/commands/doctor/shared/invalid-plugin-config.test.ts
Normal file
148
src/commands/doctor/shared/invalid-plugin-config.test.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
|
||||
|
||||
const validationMocks = vi.hoisted(() => ({
|
||||
validateConfigObjectWithPlugins: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../config/validation.js", () => ({
|
||||
validateConfigObjectWithPlugins: validationMocks.validateConfigObjectWithPlugins,
|
||||
}));
|
||||
|
||||
const { maybeRepairInvalidPluginConfig } = await import("./invalid-plugin-config.js");
|
||||
|
||||
describe("doctor invalid plugin config repair", () => {
|
||||
beforeEach(() => {
|
||||
validationMocks.validateConfigObjectWithPlugins.mockReset();
|
||||
});
|
||||
|
||||
it("disables plugins and removes invalid config payloads", () => {
|
||||
validationMocks.validateConfigObjectWithPlugins.mockReturnValue({
|
||||
ok: false,
|
||||
warnings: [],
|
||||
issues: [
|
||||
{
|
||||
path: "plugins.entries.community-feedback.config.communityRepo",
|
||||
message: 'invalid config: must match pattern "^[^/]+/[^/]+$"',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = maybeRepairInvalidPluginConfig({
|
||||
plugins: {
|
||||
entries: {
|
||||
"community-feedback": {
|
||||
enabled: true,
|
||||
config: {
|
||||
communityRepo: "",
|
||||
},
|
||||
},
|
||||
whatsapp: {
|
||||
enabled: true,
|
||||
config: {
|
||||
session: "keep",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
expect(result.changes).toEqual([
|
||||
"- plugins.entries: quarantined 1 invalid plugin config (community-feedback)",
|
||||
]);
|
||||
expect(result.config.plugins?.entries?.["community-feedback"]).toEqual({
|
||||
enabled: false,
|
||||
});
|
||||
expect(result.config.plugins?.entries?.whatsapp).toEqual({
|
||||
enabled: true,
|
||||
config: {
|
||||
session: "keep",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("handles slash-delimited plugin ids", () => {
|
||||
validationMocks.validateConfigObjectWithPlugins.mockReturnValue({
|
||||
ok: false,
|
||||
warnings: [],
|
||||
issues: [
|
||||
{
|
||||
path: "plugins.entries.pack/one.config.repo",
|
||||
message: "invalid config: must NOT have fewer than 1 characters",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = maybeRepairInvalidPluginConfig({
|
||||
plugins: {
|
||||
entries: {
|
||||
"pack/one": {
|
||||
config: {
|
||||
repo: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
expect(result.config.plugins?.entries?.["pack/one"]).toEqual({
|
||||
enabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("disables plugins whose required config payload is missing", () => {
|
||||
validationMocks.validateConfigObjectWithPlugins.mockReturnValue({
|
||||
ok: false,
|
||||
warnings: [],
|
||||
issues: [
|
||||
{
|
||||
path: "plugins.entries.community-feedback.config.communityRepo",
|
||||
message: 'invalid config: must have required property "communityRepo"',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = maybeRepairInvalidPluginConfig({
|
||||
plugins: {
|
||||
entries: {
|
||||
"community-feedback": {
|
||||
enabled: true,
|
||||
hooks: {
|
||||
allowPromptInjection: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
|
||||
expect(result.changes).toEqual([
|
||||
"- plugins.entries: quarantined 1 invalid plugin config (community-feedback)",
|
||||
]);
|
||||
expect(result.config.plugins?.entries?.["community-feedback"]).toEqual({
|
||||
enabled: false,
|
||||
hooks: {
|
||||
allowPromptInjection: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores non-plugin validation issues", () => {
|
||||
validationMocks.validateConfigObjectWithPlugins.mockReturnValue({
|
||||
ok: false,
|
||||
warnings: [],
|
||||
issues: [
|
||||
{
|
||||
path: "gateway.mode",
|
||||
message: "Expected 'local' or 'remote'",
|
||||
},
|
||||
],
|
||||
});
|
||||
const cfg = {
|
||||
gateway: {
|
||||
mode: "invalid",
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
|
||||
expect(maybeRepairInvalidPluginConfig(cfg)).toEqual({ config: cfg, changes: [] });
|
||||
});
|
||||
});
|
||||
78
src/commands/doctor/shared/invalid-plugin-config.ts
Normal file
78
src/commands/doctor/shared/invalid-plugin-config.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { OpenClawConfig } from "../../../config/types.openclaw.js";
|
||||
import { validateConfigObjectWithPlugins } from "../../../config/validation.js";
|
||||
import { sanitizeForLog } from "../../../terminal/ansi.js";
|
||||
import { asObjectRecord } from "./object.js";
|
||||
|
||||
type InvalidPluginConfigHit = {
|
||||
pluginId: string;
|
||||
pathLabel: string;
|
||||
};
|
||||
|
||||
const PLUGIN_CONFIG_ISSUE_RE = /^plugins\.entries\.([^.]+)\.config(?:\.|$)/;
|
||||
|
||||
function scanInvalidPluginConfig(cfg: OpenClawConfig): InvalidPluginConfigHit[] {
|
||||
const validation = validateConfigObjectWithPlugins(cfg);
|
||||
if (validation.ok) {
|
||||
return [];
|
||||
}
|
||||
const hits: InvalidPluginConfigHit[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const issue of validation.issues) {
|
||||
if (!issue.message.startsWith("invalid config:")) {
|
||||
continue;
|
||||
}
|
||||
const match = issue.path.match(PLUGIN_CONFIG_ISSUE_RE);
|
||||
const pluginId = match?.[1];
|
||||
if (!pluginId || seen.has(pluginId)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(pluginId);
|
||||
hits.push({
|
||||
pluginId,
|
||||
pathLabel: `plugins.entries.${pluginId}.config`,
|
||||
});
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
export function maybeRepairInvalidPluginConfig(cfg: OpenClawConfig): {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
} {
|
||||
const hits = scanInvalidPluginConfig(cfg);
|
||||
if (hits.length === 0) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
const next = structuredClone(cfg);
|
||||
const entries = asObjectRecord(next.plugins?.entries);
|
||||
if (!entries) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
const quarantined: string[] = [];
|
||||
for (const hit of hits) {
|
||||
const entry = asObjectRecord(entries[hit.pluginId]);
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
if ("config" in entry) {
|
||||
delete entry.config;
|
||||
}
|
||||
entry.enabled = false;
|
||||
quarantined.push(hit.pluginId);
|
||||
}
|
||||
|
||||
if (quarantined.length === 0) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
config: next,
|
||||
changes: [
|
||||
sanitizeForLog(
|
||||
`- plugins.entries: quarantined ${quarantined.length} invalid plugin config${quarantined.length === 1 ? "" : "s"} (${quarantined.join(", ")})`,
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,12 @@ vi.mock("../config/config.js", () => ({
|
||||
readConfigFileSnapshot: vi.fn(),
|
||||
recoverConfigFromLastKnownGood: vi.fn(),
|
||||
recoverConfigFromJsonRootSuffix: vi.fn(),
|
||||
isPluginLocalInvalidConfigSnapshot: vi.fn((snapshot: ConfigFileSnapshot) => {
|
||||
if (snapshot.valid || snapshot.legacyIssues.length > 0 || snapshot.issues.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return snapshot.issues.every((issue) => issue.path.startsWith("plugins.entries."));
|
||||
}),
|
||||
shouldAttemptLastKnownGoodRecovery: vi.fn((snapshot: ConfigFileSnapshot) => {
|
||||
if (snapshot.valid) {
|
||||
return false;
|
||||
@@ -125,7 +131,7 @@ describe("gateway startup config recovery", () => {
|
||||
expect(recoveryNotice.enqueueConfigRecoveryNotice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not restore last-known-good for plugin-local startup invalidity", async () => {
|
||||
it("continues startup in degraded mode for plugin-local startup invalidity", async () => {
|
||||
const invalidSnapshot = buildTestConfigSnapshot({
|
||||
path: configPath,
|
||||
exists: true,
|
||||
@@ -171,16 +177,82 @@ describe("gateway startup config recovery", () => {
|
||||
minimalTestGateway: true,
|
||||
log,
|
||||
}),
|
||||
).rejects.toThrow(`Invalid config at ${configPath}.`);
|
||||
).resolves.toEqual({
|
||||
snapshot: expect.objectContaining({
|
||||
valid: true,
|
||||
issues: [],
|
||||
warnings: invalidSnapshot.issues,
|
||||
}),
|
||||
wroteConfig: false,
|
||||
degradedPluginConfig: true,
|
||||
});
|
||||
|
||||
expect(configIo.recoverConfigFromLastKnownGood).not.toHaveBeenCalled();
|
||||
expect(configIo.recoverConfigFromJsonRootSuffix).toHaveBeenCalledWith(invalidSnapshot);
|
||||
expect(configIo.recoverConfigFromJsonRootSuffix).not.toHaveBeenCalled();
|
||||
expect(log.warn).toHaveBeenCalledWith(
|
||||
`gateway: last-known-good recovery skipped for plugin-local config invalidity: ${configPath}`,
|
||||
`gateway: skipped plugin config validation issue at plugins.entries.feishu: plugin feishu: plugin requires OpenClaw >=2026.4.23, but this host is 2026.4.22; skipping load. Run "openclaw doctor --fix" to quarantine the plugin config.`,
|
||||
);
|
||||
expect(recoveryNotice.enqueueConfigRecoveryNotice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps mixed plugin and core startup invalidity fatal", async () => {
|
||||
const invalidSnapshot = buildTestConfigSnapshot({
|
||||
path: configPath,
|
||||
exists: true,
|
||||
raw: `${JSON.stringify({
|
||||
gateway: { mode: "invalid" },
|
||||
plugins: {
|
||||
entries: {
|
||||
feishu: { enabled: true },
|
||||
},
|
||||
},
|
||||
})}\n`,
|
||||
parsed: {
|
||||
gateway: { mode: "invalid" },
|
||||
plugins: {
|
||||
entries: {
|
||||
feishu: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
valid: false,
|
||||
config: {
|
||||
gateway: { mode: "invalid" },
|
||||
plugins: {
|
||||
entries: {
|
||||
feishu: { enabled: true },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
issues: [
|
||||
{
|
||||
path: "gateway.mode",
|
||||
message: "Expected 'local' or 'remote'",
|
||||
},
|
||||
{
|
||||
path: "plugins.entries.feishu.config.token",
|
||||
message: "invalid config: must be string",
|
||||
},
|
||||
],
|
||||
legacyIssues: [],
|
||||
});
|
||||
vi.mocked(configIo.readConfigFileSnapshot).mockResolvedValueOnce(invalidSnapshot);
|
||||
vi.mocked(configIo.recoverConfigFromLastKnownGood).mockResolvedValueOnce(false);
|
||||
vi.mocked(configIo.recoverConfigFromJsonRootSuffix).mockResolvedValueOnce(false);
|
||||
|
||||
await expect(
|
||||
loadGatewayStartupConfigSnapshot({
|
||||
minimalTestGateway: true,
|
||||
log: { info: vi.fn(), warn: vi.fn() },
|
||||
}),
|
||||
).rejects.toThrow(`Invalid config at ${configPath}.`);
|
||||
|
||||
expect(configIo.recoverConfigFromLastKnownGood).toHaveBeenCalledWith({
|
||||
snapshot: invalidSnapshot,
|
||||
reason: "startup-invalid-config",
|
||||
});
|
||||
});
|
||||
|
||||
it("skips providers with stale model api enum values during startup", async () => {
|
||||
const config = {
|
||||
gateway: { mode: "local" },
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
recoverConfigFromLastKnownGood,
|
||||
recoverConfigFromJsonRootSuffix,
|
||||
replaceConfigFile,
|
||||
isPluginLocalInvalidConfigSnapshot,
|
||||
shouldAttemptLastKnownGoodRecovery,
|
||||
validateConfigObjectWithPlugins,
|
||||
} from "../config/config.js";
|
||||
@@ -59,6 +60,7 @@ export type GatewayStartupConfigSnapshotLoadResult = {
|
||||
snapshot: ConfigFileSnapshot;
|
||||
wroteConfig: boolean;
|
||||
degradedProviderApi?: boolean;
|
||||
degradedPluginConfig?: boolean;
|
||||
};
|
||||
|
||||
const MODEL_PROVIDER_API_PATH_RE = /^models\.providers\.([^.]+)\.api$/;
|
||||
@@ -151,6 +153,37 @@ function resolveGatewayStartupConfigWithoutInvalidModelProviders(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function resolveGatewayStartupConfigWithoutInvalidPluginEntries(params: {
|
||||
snapshot: ConfigFileSnapshot;
|
||||
log: GatewayStartupLog;
|
||||
}): ConfigFileSnapshot | null {
|
||||
if (!isPluginLocalInvalidConfigSnapshot(params.snapshot)) {
|
||||
return null;
|
||||
}
|
||||
const validated = validateConfigObjectWithPlugins(params.snapshot.sourceConfig, {
|
||||
pluginValidation: "skip",
|
||||
});
|
||||
if (!validated.ok) {
|
||||
return null;
|
||||
}
|
||||
const runtimeConfig = materializeRuntimeConfig(validated.config, "load");
|
||||
for (const issue of params.snapshot.issues) {
|
||||
params.log.warn(
|
||||
`gateway: skipped plugin config validation issue at ${issue.path}: ${issue.message}. Run "openclaw doctor --fix" to quarantine the plugin config.`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
...params.snapshot,
|
||||
sourceConfig: asResolvedSourceConfig(validated.config),
|
||||
resolved: asResolvedSourceConfig(validated.config),
|
||||
valid: true,
|
||||
runtimeConfig,
|
||||
config: runtimeConfig,
|
||||
issues: [],
|
||||
warnings: [...params.snapshot.warnings, ...params.snapshot.issues],
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadGatewayStartupConfigSnapshot(params: {
|
||||
minimalTestGateway: boolean;
|
||||
log: GatewayStartupLog;
|
||||
@@ -158,6 +191,7 @@ export async function loadGatewayStartupConfigSnapshot(params: {
|
||||
let configSnapshot = await readConfigFileSnapshot();
|
||||
let wroteConfig = false;
|
||||
let degradedStartupConfig = false;
|
||||
let degradedPluginConfig = false;
|
||||
if (configSnapshot.legacyIssues.length > 0 && isNixMode) {
|
||||
throw new Error(
|
||||
"Legacy config entries detected while running in Nix mode. Update your Nix config to the latest schema and restart.",
|
||||
@@ -174,6 +208,16 @@ export async function loadGatewayStartupConfigSnapshot(params: {
|
||||
configSnapshot = providerApiPrunedSnapshot;
|
||||
}
|
||||
}
|
||||
if (!configSnapshot.valid) {
|
||||
const pluginConfigDegradedSnapshot = resolveGatewayStartupConfigWithoutInvalidPluginEntries({
|
||||
snapshot: configSnapshot,
|
||||
log: params.log,
|
||||
});
|
||||
if (pluginConfigDegradedSnapshot) {
|
||||
degradedPluginConfig = true;
|
||||
configSnapshot = pluginConfigDegradedSnapshot;
|
||||
}
|
||||
}
|
||||
if (!configSnapshot.valid) {
|
||||
const canRecoverFromLastKnownGood = shouldAttemptLastKnownGoodRecovery(configSnapshot);
|
||||
const recovered = canRecoverFromLastKnownGood
|
||||
@@ -214,7 +258,7 @@ export async function loadGatewayStartupConfigSnapshot(params: {
|
||||
}
|
||||
|
||||
const autoEnable =
|
||||
params.minimalTestGateway || degradedStartupConfig
|
||||
params.minimalTestGateway || degradedStartupConfig || degradedPluginConfig
|
||||
? { config: configSnapshot.config, changes: [] as string[] }
|
||||
: applyPluginAutoEnable({ config: configSnapshot.config, env: process.env });
|
||||
if (autoEnable.changes.length === 0) {
|
||||
@@ -222,6 +266,7 @@ export async function loadGatewayStartupConfigSnapshot(params: {
|
||||
snapshot: configSnapshot,
|
||||
wroteConfig,
|
||||
...(degradedStartupConfig ? { degradedProviderApi: true } : {}),
|
||||
...(degradedPluginConfig ? { degradedPluginConfig: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -244,6 +289,7 @@ export async function loadGatewayStartupConfigSnapshot(params: {
|
||||
snapshot: configSnapshot,
|
||||
wroteConfig,
|
||||
...(degradedStartupConfig ? { degradedProviderApi: true } : {}),
|
||||
...(degradedPluginConfig ? { degradedPluginConfig: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user