zylon-ai/private-gpt · error · ValueError

must be a list or comma-separated string

Error message

must be a list or comma-separated string

What it means

Pydantic field_validator error on the forwarded_headers/forwarded_cookies settings: these fields accept either a list of strings or a single comma-separated string (for env vars), and anything else — a number, dict, boolean, or null-ish scalar — is rejected with 'must be a list or comma-separated string'. Valid string inputs are split, trimmed, and lowercased.

Source

Thrown at private_gpt/settings/settings.py:1641

        default_factory=lambda: ["authorization", "x-api-key"],
        description="HTTP request headers to capture in the Principal. "
        "When set via env var, use a comma-separated string: "
        "'authorization, x-custom-header'.",
    )
    forwarded_cookies: list[str] = Field(
        default_factory=list,
        description="HTTP request cookies to capture in the Principal. "
        "When set via env var, use a comma-separated string: "
        "'session, csrf-token'.",
    )

    @field_validator("forwarded_headers", "forwarded_cookies", mode="before")
    @classmethod
    def _parse_list(cls, value: object) -> list[str]:
        if isinstance(value, str):
            return [h.strip().lower() for h in value.split(",") if h.strip()]
        if not isinstance(value, list):
            raise ValueError("must be a list or comma-separated string")
        return [str(h).strip().lower() for h in value if h]


class BashSettings(BaseModel):
    cpu_limit_seconds: int = Field(
        default=30,
        description="RLIMIT_CPU applied to each isolated bash subprocess.",
    )
    memory_limit_mb: int = Field(
        default=512,
        description="RLIMIT_AS in MB applied to each isolated bash subprocess.",
    )
    fsize_limit_mb: int = Field(
        default=50,
        description="RLIMIT_FSIZE in MB applied to each isolated bash subprocess.",
    )
    nproc_limit: int = Field(
        default=50,

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Use a YAML list: forwarded_headers: [x-forwarded-for, x-request-id].
  2. Or a comma-separated env string: PRIVATE_GPT_*_FORWARDED_HEADERS='session,csrf-token'.
  3. Check YAML indentation — a nested map under the key will parse as a dict and fail.
  4. If templating config, assert the rendered value is either a JSON array or a plain comma-joined string.

Example fix

# before (settings.yaml)
forwarded_headers:
  x-forwarded-for: true   # parses as dict -> error
# after
forwarded_headers:
  - x-forwarded-for
  - x-request-id
Defensive patterns

Strategy: type-guard

Validate before calling

def parse_listlike(value):
    if isinstance(value, str):
        return [h.strip().lower() for h in value.split(',') if h.strip()]
    if isinstance(value, list):
        return [str(h).strip().lower() for h in value if h]
    raise ValueError('must be a list or comma-separated string')

Type guard

const isListOrCsv = (v: unknown): v is string | string[] =>
  typeof v === 'string' || Array.isArray(v);

Prevention

When it happens

Trigger: Setting PRIVATE_GPT_*_FORWARDED_HEADERS='session, csrf-token' (OK) versus forwarding a JSON object, a semicolon-separated string, or a bare non-string scalar via YAML/env; YAML unquoted values that parse as other types (e.g. `forwarded_cookies: {a: 1}` or a numeric value); a list containing non-string items is tolerated (str() coerced), but a top-level non-list/non-string is not.

Common situations: YAML indentation mistakes turning a list into a nested mapping; env formatters joining with ';' or JSON instead of ','; passing true/false or integers by accident; config generated by templating engines that emit dicts.

Related errors


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