{"record":{"id":"7ac4e16078fdbfb0","repo":"lfnovo/open-notebook","slug":"episode-profile-profile-id-not-found","errorCode":null,"errorMessage":"Episode profile '{profile_id}' not found","messagePattern":"Episode profile '(.+?)' not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"api/routers/episode_profiles.py","lineNumber":185,"sourceCode":"    except HTTPException:\n        raise\n    except OpenNotebookError:\n        raise\n    except Exception as e:\n        logger.error(f\"Failed to create episode profile: {e}\")\n        raise HTTPException(\n            status_code=500, detail=\"Failed to create episode profile\"\n        )\n\n\n@router.put(\"/episode-profiles/{profile_id}\", response_model=EpisodeProfileResponse)\nasync def update_episode_profile(profile_id: str, profile_data: EpisodeProfileCreate):\n    \"\"\"Update an existing episode profile\"\"\"\n    try:\n        profile = await EpisodeProfile.get(profile_id)\n\n        if not profile:\n            raise HTTPException(\n                status_code=404, detail=f\"Episode profile '{profile_id}' not found\"\n            )\n\n        update_data = profile_data.model_dump(exclude_unset=True)\n        speaker_name: Optional[str] = None\n        if \"speaker_config\" in update_data:\n            speaker = await _resolve_speaker_config(update_data[\"speaker_config\"])\n            update_data[\"speaker_config\"] = str(speaker.id)\n            speaker_name = speaker.name\n        for field, value in update_data.items():\n            setattr(profile, field, value)\n\n        await profile.save()\n        if speaker_name is None:\n            speaker_name = await _speaker_name_for(profile.speaker_config)\n        return _profile_to_response(profile, speaker_name)\n\n    except HTTPException:","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/lfnovo/open-notebook/blob/a7de90d38aaf18ee85fd661854d35c11e44613e2/api/routers/episode_profiles.py#L167-L203","documentation":"HTTP 404 from PUT /api/episode-profiles/{profile_id} when no profile with that record id exists.","triggerScenarios":"Updating a profile that was deleted, using a name instead of a record id in the URL, or an id from a different database/environment.","commonSituations":"Stale profile list in the UI after deletion in another session; copying ids between environments where SurrealDB record ids differ; passing the profile name where the record id is expected.","solutions":["GET /api/episode-profiles and use the record id (not name) from the response","Confirm the profile still exists (not deleted concurrently)","Ensure you're pointing at the right environment/database"],"exampleFix":"// before\nPUT /api/episode-profiles/my-profile-name\n// after\nPUT /api/episode-profiles/episode_profile:xk3d9z","handlingStrategy":"validation","validationCode":"profiles = (await client.get(\"/api/episode-profiles\")).json()\nids = {p[\"id\"] for p in profiles}\nif profile_id not in ids:\n    await refresh_profiles()  # stale reference\n    raise ValueError(f\"profile {profile_id} no longer exists\")","typeGuard":"def is_profile_id(pid: str, profiles: list[dict]) -> bool:\n    return pid in {p[\"id\"] for p in profiles}","tryCatchPattern":"try:\n    await client.put(f\"/api/episode-profiles/{profile_id}\", json=payload)\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 404:\n        await refresh_profile_list()  # profile gone; re-select\n    else:\n        raise","preventionTips":["Use record ids from the listing endpoint, never names, for update URLs","Re-fetch the profile before editing to detect concurrent deletion","Scope ids per environment — don't copy between databases"],"tags":["http-404","not-found","episode-profile","update"],"backgroundTag":"resource-not-found","analyzedSha":"a7de90d38aaf18ee85fd661854d35c11e44613e2","analyzedAt":"2026-08-27T02:39:58.166Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}