{"record":{"id":"8504848a8dc30ee4","repo":"zylon-ai/private-gpt","slug":"oneof-must-be-an-array-of-schemas","errorCode":null,"errorMessage":"'oneOf' must be an array of schemas","messagePattern":"'oneOf' must be an array of schemas","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"private_gpt/chat/schema_models.py","lineNumber":95,"sourceCode":"    sanitized = _handle_dunder_names(sanitized)\n    sanitized = _handle_python_keywords(sanitized)\n    sanitized = _handle_reserved_basemodel_attributes(sanitized)\n\n    return sanitized\n\n\ndef _validate_json_schema_item(schema: dict[str, Any], strict: bool = True) -> None:\n    \"\"\"Validate that the schema is a valid JSON Schema object.\"\"\"\n    if not isinstance(schema, dict):\n        raise ValueError(\"Schema must be a dictionary representing JSON Schema\")\n\n    if schema == {}:\n        return\n\n    # Handle combinators first\n    if \"oneOf\" in schema:\n        if not isinstance(schema[\"oneOf\"], list):\n            raise ValueError(\"'oneOf' must be an array of schemas\")\n        for sub_schema in schema[\"oneOf\"]:\n            _validate_json_schema_item(sub_schema, strict=False)\n        return  # oneOf schemas don't need type field\n\n    if \"anyOf\" in schema:\n        if not isinstance(schema[\"anyOf\"], list):\n            raise ValueError(\"'anyOf' must be an array of schemas\")\n        for sub_schema in schema[\"anyOf\"]:\n            _validate_json_schema_item(sub_schema, strict=False)\n        return  # anyOf schemas don't need type field\n\n    if \"allOf\" in schema:\n        if not isinstance(schema[\"allOf\"], list):\n            raise ValueError(\"'allOf' must be an array of schemas\")\n        for sub_schema in schema[\"allOf\"]:\n            _validate_json_schema_item(sub_schema, strict=False)\n        return  # allOf schemas don't need type field\n","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/chat/schema_models.py#L77-L113","documentation":"Pydantic field-validator error on SkillFile.path when the value starts with '/'. Absolute paths are rejected to keep skill files inside the skill root; the shared message ('path must be relative and cannot contain ..'') covers the absolute-path case as well. Surfaces as a 422 from FastAPI request validation.","triggerScenarios":"POST/PATCH a skill (or version) with a files[] entry whose path is '/SKILL.md' or any leading-slash path; client sending OS-native absolute paths.","commonSituations":"Frontend passing a file's absolute filesystem path instead of its key within the skill bundle; zip extraction code that preserves leading slashes; scripts ported from an API that allowed absolute paths.","solutions":["Strip the leading slash and any root prefix: send path relative to the skill root (e.g. 'SKILL.md', 'assets/logo.png').","When zipping/uploading, map each file to its arcname within the bundle.","Add client-side validation mirroring the server rules before submit."],"exampleFix":"# before\n{\"path\": \"/home/me/skills/acme/SKILL.md\", \"content_base64\": \"...\"}\n\n# after\n{\"path\": \"SKILL.md\", \"content_base64\": \"...\"}","handlingStrategy":"validation","validationCode":"function toSkillPath(raw) {\n  if (raw.startsWith('/')) throw new Error('absolute path not allowed');\n  if (raw.includes('\\\\')) throw new Error(\"use '/' separators\");\n  if (raw.split('/').includes('..')) throw new Error('traversal not allowed');\n  return raw;\n}","typeGuard":"const isRelativeSkillPath = (p) =>\n  typeof p === 'string' && p.length >= 1 && p.length <= 512 &&\n  !p.startsWith('/') && !p.includes('\\\\') && !p.split('/').includes('..');","tryCatchPattern":null,"preventionTips":["Always send paths relative to the skill root (e.g. 'SKILL.md')","Use zip arcnames when batch-importing skill bundles","Mirror the server validator client-side to fail fast with better messages"],"tags":["pydantic","http-422","validation","skills","path-handling"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}