anthropics/skills · warning · ValueError

Expected JSON object with 'reviews' key

Error message

Expected JSON object with 'reviews' key

What it means

Raised inside the eval-viewer's POST /api/feedback handler when the request body parses as JSON but is not an object containing a 'reviews' key. The ValueError is caught and returned to the browser as an HTTP 500 with the message in the JSON error field — so the client sees 500 even though it is really a 400-class bad-request problem.

Source

Thrown at skills/skill-creator/eval-viewer/generate_review.py:368

            data = b"{}"
            if self.feedback_path.exists():
                data = self.feedback_path.read_bytes()
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)
        else:
            self.send_error(404)

    def do_POST(self) -> None:
        if self.path == "/api/feedback":
            length = int(self.headers.get("Content-Length", 0))
            body = self.rfile.read(length)
            try:
                data = json.loads(body)
                if not isinstance(data, dict) or "reviews" not in data:
                    raise ValueError("Expected JSON object with 'reviews' key")
                self.feedback_path.write_text(json.dumps(data, indent=2) + "\n")
                resp = b'{"ok":true}'
                self.send_response(200)
            except (json.JSONDecodeError, OSError, ValueError) as e:
                resp = json.dumps({"error": str(e)}).encode()
                self.send_response(500)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(resp)))
            self.end_headers()
            self.wfile.write(resp)
        else:
            self.send_error(404)

    def log_message(self, format: str, *args: object) -> None:
        # Suppress request logging to keep terminal clean
        pass

View on GitHub (pinned to f6656c1256)

Solutions

  1. POST a JSON object of the shape {"reviews": [...]} with Content-Type: application/json
  2. Inspect the response body's "error" field — it echoes the exact parse/validation failure
  3. If you control the server, mirror the payload the browser UI sends (capture it in devtools) before writing your own client

Example fix

// before
await fetch('/api/feedback', { method: 'POST', body: JSON.stringify(items) });
// after
await fetch('/api/feedback', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ reviews: items }),
});
Defensive patterns

Strategy: validation

Validate before calling

def is_valid_feedback_payload(raw: bytes) -> bool:
    try:
        data = json.loads(raw)
    except json.JSONDecodeError:
        return False
    return isinstance(data, dict) and "reviews" in data and isinstance(data["reviews"], list)

Type guard

def is_feedback_obj(v) -> bool:
    return isinstance(v, dict) and isinstance(v.get("reviews"), list)

Try / catch

resp = requests.post(url + "/api/feedback", json={"reviews": reviews})
if resp.status_code != 200:
    raise RuntimeError(f"feedback rejected: {resp.json().get('error')}")

Prevention

When it happens

Trigger: POSTing to /api/feedback with a JSON array, a bare string/number, or an object without 'reviews' (e.g. {"feedback": ...}); sending an empty body when Content-Length defaults to 0 (json.loads(b'') raises JSONDecodeError into the same 500 path); malformed JSON from a hand-rolled fetch() call.

Common situations: Custom tooling or curl experiments that guess the payload shape instead of reusing the viewer's own save routine; proxies stripping the request body; the client sending form-encoded data where JSON is expected.

Related errors


AI-assisted analysis of anthropics/skills@f6656c1256 (2026-08-14). Data as JSON: /api/errors/e775d374ad6ff60f. Report an issue: GitHub.