warpdotdev/warp · error · ValueError

Expected JSON object with 'reviews' key

Error message

Expected JSON object with 'reviews' key

What it means

The eval-viewer's local HTTP server accepts POSTs only at /api/feedback and requires the body to be a JSON object containing a 'reviews' key; anything else raises this ValueError and the server responds 500 with {"error": "Expected JSON object with 'reviews' key"}. The payload is written verbatim to the feedback file, so the top-level shape is fixed.

Source

Thrown at resources/bundled/skills/create-skill/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 e72fd7aacb)

Solutions

  1. Wrap the payload: curl -X POST -d '{"reviews": [...]}' http://localhost:PORT/api/feedback
  2. Send a correct Content-Length (the server reads exactly that many bytes) and Content-Type: application/json
  3. On any 500, read the response body — the error field echoes the exact reason

Example fix

# before
curl -X POST -d '[{"file":"q1","pass":true}]' http://localhost:8765/api/feedback
# 500 {"error": "Expected JSON object with 'reviews' key"}

# after
curl -X POST -H 'Content-Type: application/json' \
  -d '{"reviews": [{"file":"q1","pass":true}]}' \
  http://localhost:8765/api/feedback
# {"ok":true}
Defensive patterns

Strategy: validation

Validate before calling

if (!Array.isArray(reviews)) {
  throw new TypeError('reviews must be an array')
}
const body = JSON.stringify({ reviews })
const res = await fetch('/api/feedback', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body,
})

Type guard

function isFeedbackPayload(value) {
  return typeof value === 'object' && value !== null && Array.isArray(value.reviews)
}

Try / catch

const res = await fetch('/api/feedback', { method: 'POST', body })
if (!res.ok) {
  const { error } = await res.json()
  throw new Error(`feedback rejected: ${error}`)
}

Prevention

When it happens

Trigger: Posting a JSON array of reviews, an object keyed 'review', 'results', or 'evaluations', or a bare single review object — anything but {"reviews": ...}. (Malformed JSON raises JSONDecodeError instead and 500s with that different message; a wrong path 404s.)

Common situations: curl scripts written against a different viewer version; a frontend form serializing the array directly instead of wrapping it; pasting an eval-results file (different top-level shape) as the feedback body.

Related errors


AI-assisted analysis of warpdotdev/warp@e72fd7aacb (2026-08-16). Data as JSON: /api/errors/fd7fda9a5c438753. Report an issue: GitHub.