can1357/oh-my-pi · error · ConfigError

Validate(${name})

Error message

Validate(${name})

What it means

withValidation lets you attach a named auxiliary validator on top of the schema. If your validate callback throws, the library wraps the throw into a ConfigError whose stage is `Validate(name)`, attributing the failure to your named check rather than schema parsing. The original error is preserved as the cause (err).

Source

Thrown at packages/coding-agent/src/config/config-file.ts:227

			throw err;
		}
	}

	async getMtimeMsAsync(): Promise<number | null> {
		const file = Bun.file(this.path());
		if (!(await file.exists())) return null;
		const lm = file.lastModified;
		return typeof lm === "number" && Number.isFinite(lm) ? lm : null;
	}

	withValidation(name: string, validate: (value: T) => void): this {
		const prev = this.#auxValidate;
		this.#auxValidate = (value: T) => {
			prev?.(value);
			try {
				validate(value);
			} catch (error) {
				throw new ConfigError(this.id, undefined, { err: error, stage: `Validate(${name})` });
			}
		};
		return this;
	}

	createDefault(): T {
		const parsed = this.schema({});
		if (!(parsed instanceof Error)) return parsed as T;
		const fallback = this.schema(undefined);
		if (!(fallback instanceof Error)) return fallback as T;
		throw new ConfigError(this.id, undefined, {
			err: new Error("Schema produced no default value"),
			stage: "createDefault",
		});
	}

	#storeCache(result: LoadResult<T>): LoadResult<T> {
		this.#cache = result;

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the wrapped `err` in the ConfigError to see the actual validation failure and fix the config value it names
  2. Fix the config so the named cross-field/semantic constraint passes (e.g. remove the conflicting key)
  3. If the validator itself is buggy, fix or loosen the callback passed to withValidation

Example fix

// before (config)
autosave: true
autosaveInterval: 0
// after (config)
autosave: true
autosaveInterval: 30
Defensive patterns

Strategy: try-catch

Validate before calling

// run the same semantic checks before load
function preValidate(value: MyConfig): string | null {
	if (value.autosave && value.autosaveInterval <= 0) return "autosaveInterval must be > 0 when autosave is on";
	return null;
}

Type guard

function isConfigError(e: unknown): e is ConfigError {
	return e instanceof ConfigError;
}

Try / catch

try {
	result = configFile.tryLoad();
} catch (err) {
	if (err instanceof ConfigError && err.stage === "Validate(crossField)") {
		logger.warn("Config failed semantic validation", { cause: err.err });
		value = configFile.createDefault();
	} else throw err;
}

Prevention

When it happens

Trigger: Registering `.withValidation("crossField", fn)` and then loading a config whose parsed value makes `fn(value)` throw — e.g. mutually exclusive keys both set, an invalid port range, a reference to a nonexistent file. Applied by tryLoad/tryLoadAsync after schema validation.

Common situations: Semantic checks a schema cannot express: conflicting settings, deprecated option combinations, cross-field range violations, or a validator that throws unexpectedly on valid-but-unusual data.

Understand the failure class

Background: Config validation failed: what "invalid value for {key}" and settings-rejection errors mean across 19 open-source libraries — this error's family across 19 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/ead786d7ee6b90fa. Report an issue: GitHub.