BigPizzaV3/CodexPlusPlus · error · ValueError

must contain a JSON object

Error message

{path} must contain a JSON object

What it means

load_json_file in codex_wechat.py loads a JSON file and raises ValueError(f"{path} must contain a JSON object") when json.load returns anything other than a dict (list, string, number, null). Config and QR-login state files must be top-level JSON objects for the bridge to work.

Solutions

  1. Edit the file so its top level is a JSON object, e.g. {"codex": {...}, ...}
  2. Validate with `python -m json.tool <path>` and check the outermost braces
  3. Regenerate the file with the tool's own save/login flow instead of hand-editing
  4. If the file is empty or corrupt, delete it and let the bridge recreate defaults

Example fix

// before (config.json)
["codex", {"base_url": "..."}]
// after
{"codex": {"base_url": "..."}}
Defensive patterns

Strategy: validation

Validate before calling

import json, pathlib
raw = json.loads(pathlib.Path(p).read_text(encoding='utf-8'))
if not isinstance(raw, dict): raise ValueError(f'{p} must contain a JSON object')

Type guard

def is_json_object(path: Path) -> bool:
    try:
        return isinstance(json.loads(path.read_text(encoding='utf-8')), dict)
    except Exception:
        return False

Try / catch

try:
    cfg = load_config(path)
except ValueError as e:
    print(f'config invalid: {e}'); sys.exit(2)

Prevention

When it happens

Trigger: load_config(path) or qr_login(path) reads a file whose parsed JSON is a top-level array, scalar, or null — e.g. config.json containing `[...]`, an empty file (JSONDecodeError path aside, null results from 'null'), or a credentials/state file written by another tool as a list.

Common situations: Hand-edited config.json that became an array; a state file truncated or overwritten with a non-object value; copying an example config with the wrong top-level shape; a QR state file written by a different version of the tool.

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 BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/ff00367638ed828c. Report an issue: GitHub.

Appendix: source

Thrown at tools/codex-wechat/codex_wechat.py:111


def log(message: str) -> None:
    line = f"{time.strftime('[%Y-%m-%d %H:%M:%S]')} {message}"
    print(line, flush=True)
    if LOG_FILE:
        try:
            LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
            with LOG_FILE.open("a", encoding="utf-8") as f:
                f.write(line + "\n")
        except Exception:
            pass


def load_json_file(path: Path) -> dict[str, Any]:
    with path.open("r", encoding="utf-8") as f:
        data = json.load(f)
    if not isinstance(data, dict):
        raise ValueError(f"{path} must contain a JSON object")
    return data


def load_config(path: Path) -> BridgeConfig:
    raw = load_json_file(path)
    codex_raw = raw.get("codex") or {}
    if not isinstance(codex_raw, dict):
        raise ValueError("codex config must be an object")

    cfg = BridgeConfig(
        base_url=str(raw.get("base_url") or ILINK_BASE_URL),
        token=str(raw.get("token") or ""),
        account_id=str(raw.get("account_id") or ""),
        allow_all=bool(raw.get("allow_all", False)),
        allow_user_ids=set(str(x) for x in raw.get("allow_user_ids") or []),
        require_prefix=bool(raw.get("require_prefix", True)),
        prefix=str(raw.get("prefix") or "/codex"),
        allow_id_command_for_unauthorized=bool(

View on GitHub (pinned to b1ed92e5e4)