{"record":{"id":"3856d267b04ddddb","repo":"odysseus-dev/odysseus","slug":"task-not-found-3856d2","errorCode":null,"errorMessage":"Task not found","messagePattern":"Task not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"routes/task_routes.py","lineNumber":580,"sourceCode":"    async def get_notifications(request: Request):\n        \"\"\"Return and clear pending task-run notifications for the\n        current user. Anonymous callers get nothing (prevents\n        cross-tenant drain — see review CRIT-B).\"\"\"\n        user = _owner(request)\n        if not user:\n            return {\"notifications\": []}\n        notes = task_scheduler.pop_notifications(owner=user)\n        return {\"notifications\": notes}\n\n    @router.post(\"/{task_id}/clear-cache\")\n    async def clear_task_cache(request: Request, task_id: str):\n        \"\"\"Clear derived cache for one built-in task.\"\"\"\n        user = _owner(request)\n        db = SessionLocal()\n        try:\n            task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()\n            if not task:\n                raise HTTPException(404, \"Task not found\")\n            if user and task.owner != user:\n                raise HTTPException(403, \"Access denied\")\n            action = task.action or \"\"\n        finally:\n            db.close()\n\n        cache_tables = {\n            \"summarize_emails\": (\"email_summaries\",),\n            \"draft_email_replies\": (\"email_ai_replies\",),\n            \"email_auto_translate\": (\"email_translations\",),\n            \"extract_email_events\": (\"email_calendar_extractions\",),\n            \"learn_sender_signatures\": (\"sender_signatures\",),\n            \"check_email_urgency\": (\"email_tags\", \"email_urgency_alerts\"),\n        }\n        tables = cache_tables.get(action)\n        if not tables:\n            raise HTTPException(400, \"This task has no clearable cache\")\n","sourceCodeStart":562,"sourceCodeEnd":598,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/task_routes.py#L562-L598","documentation":"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.","triggerScenarios":"POST clear-cache with a task_id that was deleted, a typo/truncated id, or an id from a different instance/environment.","commonSituations":"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.","solutions":["Re-fetch the task list and use a current task_id (GET /api/tasks/{id} must return 200 first).","Handle 404 in the client by refreshing or dropping the stale task row from view.","Make cache-clearing flows idempotent: treat 404 as 'nothing to clear' if that matches UX intent."],"exampleFix":"# before\nclient.post(f\"/api/tasks/{cached_id}/clear-cache\")\n\n# after\nif client.get(f\"/api/tasks/{cached_id}\").status_code == 200:\n    client.post(f\"/api/tasks/{cached_id}/clear-cache\")\nelse:\n    refresh_task_list()","handlingStrategy":"validation","validationCode":"async function taskExists(id) {\n  const res = await fetch(`/api/tasks/${encodeURIComponent(id)}`);\n  return res.status === 200;\n}","typeGuard":null,"tryCatchPattern":"try { await api.clearCache(taskId); }\ncatch (e) {\n  if (e.status === 404) { removeTaskFromView(taskId); return; } // idempotent clear\n  throw e;\n}","preventionTips":["Only show the Clear-cache action on rows from the current task list response.","Treat 404 as success (nothing to clear) for idempotent maintenance flows.","Re-sync the task list after deletions in other sessions/tabs."],"tags":["tasks","http-404","cache","fastapi"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}