datawhalechina/hello-agents · warning · HTTPException

配置必须是 JSON 对象

Error message

配置必须是 JSON 对象

What it means

Raised by PUT /config/CONFIG when the submitted content parses as valid JSON but is not an object at the top level (json.loads succeeded, isinstance(config_data, dict) failed). The stored config.json must be a mapping because the code indexes 'llm' into it, so arrays or scalars are rejected with HTTP 400 before the file is written.

Source

Thrown at Co-creation-projects/tino-chen-HelloClaw/src/api/config.py:98

        raise HTTPException(status_code=404, detail=f"配置文件 {name} 不存在")
    return {"name": name, "content": content}


@router.put("/{name}")
async def update_config(name: str, request: ConfigUpdateRequest, ws: WorkspaceManager = Depends(get_workspace)):
    """更新配置文件"""
    # 特殊处理 CONFIG (config.json)
    if name == "CONFIG":
        ensure_config_json_exists()
        # 严格校验 JSON 格式
        try:
            config_data = json.loads(request.content)
        except json.JSONDecodeError as e:
            raise HTTPException(status_code=400, detail=f"无效的 JSON 格式: {str(e)}")

        # 校验必需字段
        if not isinstance(config_data, dict):
            raise HTTPException(status_code=400, detail="配置必须是 JSON 对象")

        if "llm" not in config_data:
            raise HTTPException(status_code=400, detail="缺少必需字段: llm")

        llm_config = config_data.get("llm", {})
        required_fields = ["model_id", "api_key", "base_url"]
        missing_fields = [f for f in required_fields if f not in llm_config]
        if missing_fields:
            raise HTTPException(status_code=400, detail=f"llm 配置缺少必需字段: {', '.join(missing_fields)}")

        config_path = get_config_json_path()
        with open(config_path, "w", encoding="utf-8") as f:
            f.write(request.content)
        return {"name": name, "status": "updated"}

    # 处理 .md 配置文件
    if name not in ws.list_configs():
        raise HTTPException(status_code=404, detail=f"配置文件 {name} 不存在")

View on GitHub (pinned to 606a07d341)

Solutions

  1. Wrap the payload in curly braces so the top-level value is an object: {"llm": {...}}
  2. Check the first non-whitespace character of the content is '{' before submitting
  3. Diff against the existing config.json (GET /config/CONFIG) to confirm the expected shape

Example fix

// before
content = JSON.stringify(["llm", {model_id: "gpt-4", api_key: "sk-...", base_url: "https://api.openai.com/v1"}])
// after
content = JSON.stringify({llm: {model_id: "gpt-4", api_key: "sk-...", base_url: "https://api.openai.com/v1"}})
Defensive patterns

Strategy: validation

Validate before calling

const parsed = JSON.parse(textareaValue);
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
  showError('Top-level value must be a JSON object');
}

Type guard

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

Prevention

When it happens

Trigger: PUT /api/config/CONFIG with content like '["llm", {...}]', '"just a string"', '123', 'true', or 'null'. All parse cleanly but are not dicts.

Common situations: User wraps the config in an array thinking it is a list format; user saves a bare API-key string instead of the full object; frontend serializes the wrong variable (a value instead of the containing object).

Related errors


AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14). Data as JSON: /api/errors/08ade99ff96ce9e6. Report an issue: GitHub.