zylon-ai/private-gpt · critical · TypeError

Config file has no top-level mapping: {path}

Error message

Config file has no top-level mapping: {path}

What it means

TypeError raised by the settings loader when a located profile YAML (e.g. settings.yaml or settings-<profile>.yaml) parses to a non-dict root — the entire file must be a top-level mapping because each key becomes a settings section. A YAML file containing only a list, a scalar, or multiple bare documents produces this.

Source

Thrown at private_gpt/settings/settings_loader.py:66

def load_settings_from_profile(profile: str) -> dict[str, Any]:
    if profile == "default":
        profile_file_name = "settings.yaml"
    elif profile == "override":
        profile_file_name = "settings.override.yaml"
    else:
        profile_file_name = f"settings-{profile}.yaml"

    config: dict[str, Any] = {}
    found = False
    for settings_folder in _settings_folders:
        path = Path(settings_folder) / profile_file_name
        if not path.is_file():
            continue
        with Path(path).open("r") as f:
            raw = load_yaml_with_envvars(f)
        if not isinstance(raw, dict):
            raise TypeError(f"Config file has no top-level mapping: {path}")
        config = raw
        found = True
        break

    if not found:
        raise FileNotFoundError(
            f"Settings file not found for profile '{profile}'. "
            f"Searched in folders: {_settings_folders} with file name '{profile_file_name}'"
        )

    return config


@typing.no_type_check
def discover_models_from_environment(
    environ: dict[str, Any] = os.environ,
) -> list[dict[str, Any]]:
    """Discover model configurations from environment variables.

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Open the named path from the error and make the root a mapping: every setting must live under top-level keys (e.g. `llm:`, `ui:`, `scheduler:`).
  2. Remove stray list markers or leading documents so `yaml.safe_load(f)` returns a dict.
  3. Validate locally: `python -c "import yaml,sys; print(type(yaml.safe_load(open('settings.yaml'))))"` must print <class 'dict'>.
  4. If multiple configs were concatenated, split them into per-profile files.

Example fix

# before (settings-dev.yaml)
- llm:
    mode: local
# after (settings-dev.yaml)
llm:
  mode: local
Defensive patterns

Strategy: validation

Validate before calling

import yaml

def settings_file_is_mapping(path: str) -> bool:
    with open(path) as f:
        return isinstance(yaml.safe_load(f), dict)

Type guard

const isMapping = (v: unknown): v is Record<string, unknown> =>
  typeof v === 'object' && v !== null && !Array.isArray(v);

Prevention

When it happens

Trigger: Running with profile P where settings-P.yaml contains a top-level JSON/YAML list or plain string (e.g. a file that is just '- key: value' bullets, or a log dump); a YAML file whose first key is unindented under a list item; multiple '---' documents where the first is a scalar; an empty comment-only file is usually fine (None) but stray text is not.

Common situations: Hand-editing settings files and losing the top-level keys; converting config via a script that emitted a list of dicts; truncation/merge tools appending content that changes the root node; wrong file placed in a settings folder that the loader scans.

Related errors


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