odysseus-dev/odysseus · info · HTTPException

query is required

Error message

query is required

What it means

400 from POST /api/skills/search: body.get('query') is missing, None, or whitespace-only. The route requires a non-empty query string before running get_relevant_skills; an empty query has no ranking semantics, so it is rejected rather than returning the whole list.

Source

Thrown at routes/skills_routes.py:1656

    @router.delete("/{skill_id}")
    async def delete_skill(request: Request, skill_id: str):
        user = _owner(request)
        skills = skills_manager.load(owner=user)
        match = next((s for s in skills if s.get("name") == skill_id or s.get("id") == skill_id), None)
        if not match:
            raise HTTPException(404, "Skill not found")
        _verify_owner(match, user)
        ok = skills_manager.delete_skill(match.get("name"), owner=user)
        if not ok:
            raise HTTPException(404, "Skill not found")
        return {"ok": True}

    @router.post("/search")
    async def search_skills(request: Request):
        body = await request.json()
        query = body.get("query", "")
        if not query.strip():
            raise HTTPException(400, "query is required")
        user = _owner(request)
        skills = skills_manager.load(owner=user)
        results = skills_manager.get_relevant_skills(query, skills, max_items=10)
        return {"skills": results, "query": query, "count": len(results)}

    return router

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send a non-empty JSON body: {"query": "some text"}.
  2. Guard client-side: disable submit while the query is blank.
  3. If listing all skills is the intent, use GET /api/skills instead of an empty search.

Example fix

# before
client.post("/api/skills/search", json={"query": ""})  # 400

# after
q = input_text.strip()
if not q:
    return client.get("/api/skills").json()  # list-all fallback client-side
client.post("/api/skills/search", json={"query": q})
Defensive patterns

Strategy: validation

Validate before calling

query = (body.get("query") or "").strip() if isinstance(body, dict) else ""
if not query:
    results = client.get(f"{base}/api/skills").json()  # list-all instead of empty search
else:
    results = client.post(f"{base}/api/skills/search", json={"query": query}).json()

Type guard

def is_valid_search_body(body: dict) -> bool:
    q = body.get("query")
    return isinstance(q, str) and bool(q.strip())

Prevention

When it happens

Trigger: POST /search with {} , {"query": null}, {"query": " "}, or the field under a different key ('q', 'text'); client sending an empty search box submission.

Common situations: Search form submitted before typing; template literal producing undefined serialized as null; API consumer assuming GET-style ?q= parameter instead of the JSON body.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/7925f8c11be309df. Report an issue: GitHub.