odysseus-dev/odysseus · error · HTTPException

Task not found

Error message

Task not found

What it means

POST /api/tasks/{task_id}/clear-cache returns 404 when no ScheduledTask row with that id exists. The lookup happens before ownership and action checks, so a wrong or deleted id fails here first.

Source

Thrown at routes/task_routes.py:580

    async def get_notifications(request: Request):
        """Return and clear pending task-run notifications for the
        current user. Anonymous callers get nothing (prevents
        cross-tenant drain — see review CRIT-B)."""
        user = _owner(request)
        if not user:
            return {"notifications": []}
        notes = task_scheduler.pop_notifications(owner=user)
        return {"notifications": notes}

    @router.post("/{task_id}/clear-cache")
    async def clear_task_cache(request: Request, task_id: str):
        """Clear derived cache for one built-in task."""
        user = _owner(request)
        db = SessionLocal()
        try:
            task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()
            if not task:
                raise HTTPException(404, "Task not found")
            if user and task.owner != user:
                raise HTTPException(403, "Access denied")
            action = task.action or ""
        finally:
            db.close()

        cache_tables = {
            "summarize_emails": ("email_summaries",),
            "draft_email_replies": ("email_ai_replies",),
            "email_auto_translate": ("email_translations",),
            "extract_email_events": ("email_calendar_extractions",),
            "learn_sender_signatures": ("sender_signatures",),
            "check_email_urgency": ("email_tags", "email_urgency_alerts"),
        }
        tables = cache_tables.get(action)
        if not tables:
            raise HTTPException(400, "This task has no clearable cache")

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Re-fetch the task list and use a current task_id (GET /api/tasks/{id} must return 200 first).
  2. Handle 404 in the client by refreshing or dropping the stale task row from view.
  3. Make cache-clearing flows idempotent: treat 404 as 'nothing to clear' if that matches UX intent.

Example fix

# before
client.post(f"/api/tasks/{cached_id}/clear-cache")

# after
if client.get(f"/api/tasks/{cached_id}").status_code == 200:
    client.post(f"/api/tasks/{cached_id}/clear-cache")
else:
    refresh_task_list()
Defensive patterns

Strategy: validation

Validate before calling

async function taskExists(id) {
  const res = await fetch(`/api/tasks/${encodeURIComponent(id)}`);
  return res.status === 200;
}

Try / catch

try { await api.clearCache(taskId); }
catch (e) {
  if (e.status === 404) { removeTaskFromView(taskId); return; } // idempotent clear
  throw e;
}

Prevention

When it happens

Trigger: POST clear-cache with a task_id that was deleted, a typo/truncated id, or an id from a different instance/environment.

Common situations: UI list is stale after the task was removed elsewhere; bookmarked deep-link to a removed task; copy-paste between dev and prod databases; retrying an old request after task deletion.

Related errors


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