{"record":{"id":"ead786d7ee6b90fa","repo":"can1357/oh-my-pi","slug":"validate-name","errorCode":null,"errorMessage":"Validate(${name})","messagePattern":"Validate\\((.+?)\\)","errorType":"error_code","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/config/config-file.ts","lineNumber":227,"sourceCode":"\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync getMtimeMsAsync(): Promise<number | null> {\n\t\tconst file = Bun.file(this.path());\n\t\tif (!(await file.exists())) return null;\n\t\tconst lm = file.lastModified;\n\t\treturn typeof lm === \"number\" && Number.isFinite(lm) ? lm : null;\n\t}\n\n\twithValidation(name: string, validate: (value: T) => void): this {\n\t\tconst prev = this.#auxValidate;\n\t\tthis.#auxValidate = (value: T) => {\n\t\t\tprev?.(value);\n\t\t\ttry {\n\t\t\t\tvalidate(value);\n\t\t\t} catch (error) {\n\t\t\t\tthrow new ConfigError(this.id, undefined, { err: error, stage: `Validate(${name})` });\n\t\t\t}\n\t\t};\n\t\treturn this;\n\t}\n\n\tcreateDefault(): T {\n\t\tconst parsed = this.schema({});\n\t\tif (!(parsed instanceof Error)) return parsed as T;\n\t\tconst fallback = this.schema(undefined);\n\t\tif (!(fallback instanceof Error)) return fallback as T;\n\t\tthrow new ConfigError(this.id, undefined, {\n\t\t\terr: new Error(\"Schema produced no default value\"),\n\t\t\tstage: \"createDefault\",\n\t\t});\n\t}\n\n\t#storeCache(result: LoadResult<T>): LoadResult<T> {\n\t\tthis.#cache = result;","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/config/config-file.ts#L209-L245","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the wrapped `err` in the ConfigError to see the actual validation failure and fix the config value it names","Fix the config so the named cross-field/semantic constraint passes (e.g. remove the conflicting key)","If the validator itself is buggy, fix or loosen the callback passed to withValidation"],"exampleFix":"// before (config)\nautosave: true\nautosaveInterval: 0\n// after (config)\nautosave: true\nautosaveInterval: 30","handlingStrategy":"try-catch","validationCode":"// run the same semantic checks before load\nfunction preValidate(value: MyConfig): string | null {\n\tif (value.autosave && value.autosaveInterval <= 0) return \"autosaveInterval must be > 0 when autosave is on\";\n\treturn null;\n}","typeGuard":"function isConfigError(e: unknown): e is ConfigError {\n\treturn e instanceof ConfigError;\n}","tryCatchPattern":"try {\n\tresult = configFile.tryLoad();\n} catch (err) {\n\tif (err instanceof ConfigError && err.stage === \"Validate(crossField)\") {\n\t\tlogger.warn(\"Config failed semantic validation\", { cause: err.err });\n\t\tvalue = configFile.createDefault();\n\t} else throw err;\n}","preventionTips":["Keep withValidation callbacks deterministic and throwing clear, actionable messages","Prefer schema-level defaults/constraints over auxiliary validators when possible","Dry-run the semantic check on sample values during tests","Inspect err.err (the wrapped cause) to fix the exact offending config field"],"tags":["config","validation","semantic-validation"],"backgroundTag":"config-validation-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}