zylon-ai/private-gpt · error · ValueError

'oneOf' must be an array of schemas

Error message

'oneOf' must be an array of schemas

What it means

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.

Source

Thrown at private_gpt/chat/schema_models.py:95

    sanitized = _handle_dunder_names(sanitized)
    sanitized = _handle_python_keywords(sanitized)
    sanitized = _handle_reserved_basemodel_attributes(sanitized)

    return sanitized


def _validate_json_schema_item(schema: dict[str, Any], strict: bool = True) -> None:
    """Validate that the schema is a valid JSON Schema object."""
    if not isinstance(schema, dict):
        raise ValueError("Schema must be a dictionary representing JSON Schema")

    if schema == {}:
        return

    # Handle combinators first
    if "oneOf" in schema:
        if not isinstance(schema["oneOf"], list):
            raise ValueError("'oneOf' must be an array of schemas")
        for sub_schema in schema["oneOf"]:
            _validate_json_schema_item(sub_schema, strict=False)
        return  # oneOf schemas don't need type field

    if "anyOf" in schema:
        if not isinstance(schema["anyOf"], list):
            raise ValueError("'anyOf' must be an array of schemas")
        for sub_schema in schema["anyOf"]:
            _validate_json_schema_item(sub_schema, strict=False)
        return  # anyOf schemas don't need type field

    if "allOf" in schema:
        if not isinstance(schema["allOf"], list):
            raise ValueError("'allOf' must be an array of schemas")
        for sub_schema in schema["allOf"]:
            _validate_json_schema_item(sub_schema, strict=False)
        return  # allOf schemas don't need type field

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Strip the leading slash and any root prefix: send path relative to the skill root (e.g. 'SKILL.md', 'assets/logo.png').
  2. When zipping/uploading, map each file to its arcname within the bundle.
  3. Add client-side validation mirroring the server rules before submit.

Example fix

# before
{"path": "/home/me/skills/acme/SKILL.md", "content_base64": "..."}

# after
{"path": "SKILL.md", "content_base64": "..."}
Defensive patterns

Strategy: validation

Validate before calling

function toSkillPath(raw) {
  if (raw.startsWith('/')) throw new Error('absolute path not allowed');
  if (raw.includes('\\')) throw new Error("use '/' separators");
  if (raw.split('/').includes('..')) throw new Error('traversal not allowed');
  return raw;
}

Type guard

const isRelativeSkillPath = (p) =>
  typeof p === 'string' && p.length >= 1 && p.length <= 512 &&
  !p.startsWith('/') && !p.includes('\\') && !p.split('/').includes('..');

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/8504848a8dc30ee4. Report an issue: GitHub.