fix: parse config sections independently so one invalid field doesn't discard the entire config

Previously, a single validation error (e.g. wrong type for
prometheus.permission.edit) caused safeParse to fail and the
entire oh-my-opencode.json was silently replaced with {}.

Now loadConfigFromPath falls back to parseConfigPartially() which
validates each top-level key in isolation, keeps the sections that
pass, and logs which sections were skipped.

Closes #1767
This commit is contained in:
Rishi Vhavle
2026-02-12 01:33:12 +05:30
parent a476fe94b8
commit 706dd24912
2 changed files with 176 additions and 13 deletions
+55 -12
View File
@@ -11,6 +11,42 @@ import {
migrateConfigFile,
} from "./shared";
export function parseConfigPartially(
rawConfig: Record<string, unknown>
): OhMyOpenCodeConfig | null {
const fullResult = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
if (fullResult.success) {
return fullResult.data;
}
const partialConfig: Record<string, unknown> = {};
const invalidSections: string[] = [];
for (const key of Object.keys(rawConfig)) {
const sectionResult = OhMyOpenCodeConfigSchema.safeParse({ [key]: rawConfig[key] });
if (sectionResult.success) {
const parsed = sectionResult.data as Record<string, unknown>;
if (parsed[key] !== undefined) {
partialConfig[key] = parsed[key];
}
} else {
const sectionErrors = sectionResult.error.issues
.filter((i) => i.path[0] === key)
.map((i) => `${i.path.join(".")}: ${i.message}`)
.join(", ");
if (sectionErrors) {
invalidSections.push(`${key}: ${sectionErrors}`);
}
}
}
if (invalidSections.length > 0) {
log("Partial config loaded — invalid sections skipped:", invalidSections);
}
return partialConfig as OhMyOpenCodeConfig;
}
export function loadConfigFromPath(
configPath: string,
ctx: unknown
@@ -24,20 +60,27 @@ export function loadConfigFromPath(
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
if (!result.success) {
const errorMsg = result.error.issues
.map((i) => `${i.path.join(".")}: ${i.message}`)
.join(", ");
log(`Config validation error in ${configPath}:`, result.error.issues);
addConfigLoadError({
path: configPath,
error: `Validation error: ${errorMsg}`,
});
return null;
if (result.success) {
log(`Config loaded from ${configPath}`, { agents: result.data.agents });
return result.data;
}
log(`Config loaded from ${configPath}`, { agents: result.data.agents });
return result.data;
const errorMsg = result.error.issues
.map((i) => `${i.path.join(".")}: ${i.message}`)
.join(", ");
log(`Config validation error in ${configPath}:`, result.error.issues);
addConfigLoadError({
path: configPath,
error: `Partial config loaded — invalid sections skipped: ${errorMsg}`,
});
const partialResult = parseConfigPartially(rawConfig);
if (partialResult) {
log(`Partial config loaded from ${configPath}`, { agents: partialResult.agents });
return partialResult;
}
return null;
}
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err);