can1357/oh-my-pi · error · AIError.ValidationError
Schema node has no type, combinator, or $ref — cannot enforc
Error message
Schema node has no type, combinator, or $ref — cannot enforce strict mode
What it means
enforceStrictSchema's per-node pass requires every schema node to be one of: typed (type set), a combinator (anyOf/oneOf/allOf array present), a $ref, or a `not` object. A node with none of these (e.g. `{}` or `{ description: "..." }`) cannot be turned into a strict-mode schema, so AIError.ValidationError is thrown.
Source
Thrown at packages/ai/src/utils/schema/normalize.ts:2274
// combinator / `$ref` / `not`). When `type` is missing, try to infer it
// from a homogeneous-primitive `enum` / `const` so direct calls to
// `enforceStrictSchema` (which bypass `sanitizeSchemaForStrictMode`'s own
// inference pass) still produce wire-valid output.
if (result.type === undefined) {
const inferred = inferStrictPrimitiveTypeFromEnumOrConst(result);
if (inferred !== undefined) result.type = inferred;
}
// Schemas like `{}`, `{items: {}}`, mixed-primitive enums, and non-primitive
// consts are not representable in strict mode — `enum`/`const` are not
// accepted as type substitutes here because they did not yield a single
// inferable type above.
if (
result.type === undefined &&
result.$ref === undefined &&
!COMBINATOR_KEYS.some(key => Array.isArray(result[key])) &&
!isJsonObject(result.not)
) {
throw new AIError.ValidationError("Schema node has no type, combinator, or $ref — cannot enforce strict mode");
}
return result;
}
export function tryEnforceStrictSchema(schema: Record<string, unknown>): {
schema: Record<string, unknown>;
strict: boolean;
} {
return stamp(schema, kStrictSchema, s => {
const upgraded = upgradeJsonSchemaTo202012(s) as Record<string, unknown>;
if (hasUnrepresentableStrictObjectMap(upgraded)) {
return { schema: upgraded, strict: false };
}
try {
const sanitized = sanitizeSchemaForStrictMode(upgraded);
return { schema: enforceStrictSchema(sanitized), strict: true };
} catch {
return { schema: upgraded, strict: false };View on GitHub (pinned to 9690622007)
Solutions
- Give every schema node an explicit `type` (e.g. `"type": "object"`, `"type": "string"`)
- Wrap truly-any-typed nodes in a combinator such as `{ anyOf: [{type:"string"},{type:"number"},...] }`
- Use a `$ref` into `$defs` for shared/recursive nodes instead of empty placeholders
- Use tryEnforceStrictSchema to detect the offending node gracefully and fix it at build time
Example fix
// before
const schema = { type: "object", properties: { data: {} } };
// after
const schema = { type: "object", properties: { data: { type: "object", properties: {}, additionalProperties: true } } }; Defensive patterns
Strategy: validation
Validate before calling
function nodesHaveType(s: unknown): boolean {
if (typeof s !== "object" || s === null || Array.isArray(s)) return true;
const n = s as Record<string, unknown>;
const ok = n.type !== undefined || n.$ref !== undefined || ["anyOf","oneOf","allOf"].some(k => Array.isArray(n[k])) || typeof n.not === "object" && n.not !== null;
return ok && Object.values(n).every(nodesHaveType);
}
if (!nodesHaveType(schema)) throw new Error("schema node missing type"); Type guard
function isStrictableNode(n: Record<string, unknown>): boolean {
return n.type !== undefined || n.$ref !== undefined ||
["anyOf","oneOf","allOf"].some(k => Array.isArray(n[k])) ||
(typeof n.not === "object" && n.not !== null);
} Try / catch
const res = tryEnforceStrictSchema(schema);
if (!res.schema) {
console.error("strict enforcement failed:", res.error);
}
const strict = res.schema ?? schema; Prevention
- Never leave placeholder `{}` nodes in tool parameter schemas
- Give every property an explicit type or wrap in anyOf
- Run tryEnforceStrictSchema in tests before shipping tool definitions
- Enable strict provider settings only with fully typed schemas
When it happens
Trigger: Registering a tool whose parameters schema contains an empty object node (`{}`), a node with only annotations (description/title/default) and no type, or a boolean-ish placeholder object, when the provider path enforces strict schema normalization.
Common situations: Hand-written tool schemas with placeholder nodes left empty; code generators emitting `{}` for unknown/any types; schemas migrated from draft formats where an empty object means 'anything' but strict providers reject that; spreading optional fragments that drop the `type` key.
Related errors
- Schema contains a circular object graph — cannot enforce str
- anthropic-messages: ${data.summary}
- Validation failed for tool "${toolCall.name}":\n${errors}\n\
- ClinePass model catalog response is missing clinePass
- rewrite received invalid arguments: ${params.summary}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/4576cd55bdcdf0ce.
Report an issue: GitHub.