lfnovo/open-notebook · warning · HTTPException

Episode profile '{profile_id}' not found

Error message

Episode profile '{profile_id}' not found

What it means

HTTP 404 from PUT /api/episode-profiles/{profile_id} when no profile with that record id exists.

Source

Thrown at api/routers/episode_profiles.py:185

    except HTTPException:
        raise
    except OpenNotebookError:
        raise
    except Exception as e:
        logger.error(f"Failed to create episode profile: {e}")
        raise HTTPException(
            status_code=500, detail="Failed to create episode profile"
        )


@router.put("/episode-profiles/{profile_id}", response_model=EpisodeProfileResponse)
async def update_episode_profile(profile_id: str, profile_data: EpisodeProfileCreate):
    """Update an existing episode profile"""
    try:
        profile = await EpisodeProfile.get(profile_id)

        if not profile:
            raise HTTPException(
                status_code=404, detail=f"Episode profile '{profile_id}' not found"
            )

        update_data = profile_data.model_dump(exclude_unset=True)
        speaker_name: Optional[str] = None
        if "speaker_config" in update_data:
            speaker = await _resolve_speaker_config(update_data["speaker_config"])
            update_data["speaker_config"] = str(speaker.id)
            speaker_name = speaker.name
        for field, value in update_data.items():
            setattr(profile, field, value)

        await profile.save()
        if speaker_name is None:
            speaker_name = await _speaker_name_for(profile.speaker_config)
        return _profile_to_response(profile, speaker_name)

    except HTTPException:

View on GitHub (pinned to a7de90d38a)

Solutions

  1. GET /api/episode-profiles and use the record id (not name) from the response
  2. Confirm the profile still exists (not deleted concurrently)
  3. Ensure you're pointing at the right environment/database

Example fix

// before
PUT /api/episode-profiles/my-profile-name
// after
PUT /api/episode-profiles/episode_profile:xk3d9z
Defensive patterns

Strategy: validation

Validate before calling

profiles = (await client.get("/api/episode-profiles")).json()
ids = {p["id"] for p in profiles}
if profile_id not in ids:
    await refresh_profiles()  # stale reference
    raise ValueError(f"profile {profile_id} no longer exists")

Type guard

def is_profile_id(pid: str, profiles: list[dict]) -> bool:
    return pid in {p["id"] for p in profiles}

Try / catch

try:
    await client.put(f"/api/episode-profiles/{profile_id}", json=payload)
except httpx.HTTPStatusError as e:
    if e.response.status_code == 404:
        await refresh_profile_list()  # profile gone; re-select
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of lfnovo/open-notebook@a7de90d38a (2026-08-27). Data as JSON: /api/errors/7ac4e16078fdbfb0. Report an issue: GitHub.