HKUDS/Vibe-Trading · error · ValueError

invalid portfolio settings: root must be an object

Error message

invalid portfolio settings: root must be an object

What it means

Raised by SettingsStore.load when the portfolio settings JSON file parses successfully but its top-level value is not a JSON object (e.g. it is an array, string, or number). The loader expects a dict because it feeds the payload straight into parse_settings. This is a file-corruption / wrong-format error, distinct from a JSON syntax error which is raised earlier with the parser's message.

Solutions

  1. Inspect the settings file at the store's path and confirm the top level is an object like {"sources": [...]}
  2. Fix or restore the file (from backup or by deleting it to fall back to defaults, since load constructs PortfolioSettings() when the file is absent)
  3. If a custom tool writes the file, change it to json.dump({...}, ...) with a dict root
  4. Re-run settings_store.load() to confirm it parses

Example fix

// before
[{"id": "ibkr", "enabled": true}]
// after
{"sources": [{"id": "ibkr", "enabled": true}]}
Defensive patterns

Strategy: validation

Validate before calling

import json
from pathlib import Path

def settings_file_is_valid(path: Path) -> bool:
    try:
        return isinstance(json.loads(path.read_text(encoding="utf-8")), dict)
    except (OSError, json.JSONDecodeError):
        return False

Type guard

def is_settings_payload(payload: object) -> bool:
    return isinstance(payload, dict)

Try / catch

try:
    settings = store.load()
except ValueError as exc:
    if "root must be an object" in str(exc):
        # restore backup or delete file to fall back to defaults
        path.unlink(missing_ok=True)
        settings = store.load()
    else:
        raise

Prevention

When it happens

Trigger: The settings file at self.path contains valid JSON whose root is not an object, e.g. '[]', '"text"', '123', or 'null'. Happens when a user or script overwrites the file with a list of sources or raw serialized fragment instead of an object with keys like 'sources'.

Common situations: Hand-editing settings.json and wrapping everything in brackets; writing a list of sources from a custom script; a truncated or partially-written file from a crashed save; migrating from an older format that stored an array.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/555b19ed20ae9d67. Report an issue: GitHub.

Appendix: source

Thrown at agent/src/portfolio/config.py:248

        self.connection_store = connection_store or ConnectionStore(connection_path)

    def load(self) -> PortfolioSettings:
        """Read persisted settings, creating an empty file on first use.

        Returns:
            The validated settings currently on disk.

        Raises:
            ValueError: If the file is unreadable, is not a JSON object, or
                fails validation.
        """
        if self.path.exists():
            try:
                payload = json.loads(self.path.read_text(encoding="utf-8"))
            except (OSError, json.JSONDecodeError) as exc:
                raise ValueError(f"invalid portfolio settings: {exc}") from exc
            if not isinstance(payload, dict):
                raise ValueError("invalid portfolio settings: root must be an object")
            settings = parse_settings(payload, self.connection_store)
            if any("profile_id" in source for source in payload.get("sources", [])):
                self.save(settings)
            return settings

        settings = PortfolioSettings()
        self.save(settings)
        return settings

    def save(self, settings: PortfolioSettings | dict[str, Any]) -> PortfolioSettings:
        """Validate and atomically persist settings with owner-only permissions.

        Args:
            settings: Settings object or raw dict to validate and store.

        Returns:
            The validated settings that were written.

View on GitHub (pinned to 80ffdda44c)