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
- Send a non-empty JSON body: {"query": "some text"}.
- Guard client-side: disable submit while the query is blank.
- 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
- Disable search submit while the query box is empty.
- Use the exact JSON key 'query' (not 'q').
- Use GET /api/skills when the intent is listing, not searching.
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
- markdown is required
- No documents specified
- Provide at least one point, box, or object text
- Box must be [x1, y1, x2, y2]
- content is required
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/7925f8c11be309df.
Report an issue: GitHub.