{"record":{"id":"5466b7207c3e7b30","repo":"odysseus-dev/odysseus","slug":"assistant-not-found","errorCode":null,"errorMessage":"Assistant not found","messagePattern":"Assistant not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"routes/assistant_routes.py","lineNumber":168,"sourceCode":"                \"check_ins\": [_task_to_checkin_dict(t) for t in tasks],\n                \"task_ids\": [t.id for t in tasks],\n            }\n        finally:\n            db.close()\n\n    @router.patch(\"/settings\")\n    async def update_assistant_settings(payload: AssistantSettingsUpdate, request: Request):\n        \"\"\"Update CrewMember fields and/or check-in tasks in one call.\"\"\"\n        owner = _owner(request)\n        crew = await _get_or_create(owner)\n        if not crew:\n            raise HTTPException(status_code=500, detail=\"Assistant not available\")\n\n        db = SessionLocal()\n        try:\n            crew_db = db.query(CrewMember).filter(CrewMember.id == crew.id).first()\n            if not crew_db:\n                raise HTTPException(status_code=404, detail=\"Assistant not found\")\n\n            # Update CrewMember fields.\n            if payload.name is not None:\n                crew_db.name = payload.name.strip() or crew_db.name\n            if payload.avatar is not None:\n                crew_db.avatar = payload.avatar\n            if payload.personality is not None:\n                crew_db.personality = payload.personality\n            if payload.model is not None:\n                crew_db.model = payload.model or None\n            if payload.endpoint_url is not None:\n                crew_db.endpoint_url = payload.endpoint_url or None\n            if payload.timezone is not None:\n                crew_db.timezone = payload.timezone or None\n\n            # Tool list: either explicit list, or implicit toggle.\n            if payload.enabled_tools is not None:\n                crew_db.enabled_tools = json.dumps(payload.enabled_tools)","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/assistant_routes.py#L150-L186","documentation":"Raised by PATCH /api/assistant/settings with 404 when the second, write-scoped query (db.query(CrewMember).filter(CrewMember.id == crew.id)) finds no row. The crew object was resolved moments earlier by _get_or_create, so this means the row vanished between the resolution read and the re-query inside the PATCH — a delete race, not a bad payload.","triggerScenarios":"An admin-wipe or user-deletion flow deletes the CrewMember between _get_or_create and the PATCH's re-query; the app was restarted against a different/blank database file between the two reads; a concurrent settings PATCH deleted and recreated the assistant row.","commonSituations":"User clicks Save in the settings UI at the same moment an admin wipe or account deletion runs; multi-process deployment where one process points at a different SQLite file; long-idle settings page whose underlying row was removed.","solutions":["Have the client re-GET /api/assistant/settings (re-seeds the assistant) and re-apply the PATCH.","Check whether an admin wipe / user deletion ran concurrently; if so, re-authenticate and reload settings first.","Verify all worker processes share the same database file (single SQLite path, no per-process copies).","If reproducible, capture the row's existence right before the PATCH — a repeat failure indicates a deletion job, not a FastAPI bug."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const r = await fetch('/api/assistant/settings', {method: 'PATCH', ...});\n  if (r.status === 404) {\n    // row vanished mid-request: reseed via GET, then re-apply\n    await fetch('/api/assistant/session', {credentials: 'include'});\n    return fetch('/api/assistant/settings', {method: 'PATCH', ...});\n  }\n  return r;\n} catch (e) { throw e; }","preventionTips":["Don't keep settings pages open for hours before saving — re-GET immediately before PATCH.","Serialize admin-wipe/user-deletion with in-flight settings writes.","Run a single app process per database file to avoid split-brain deletes."],"tags":["fastapi","race-condition","assistant","settings","database"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}