odysseus-dev/odysseus · error · ValueError

empty uid

Error message

empty uid

What it means

ValueError('empty uid') raised by _resolve_base_uid when called with an empty (or None-after-truthiness) uid string. This helper splits compound recurrence uids of the form '{base}::{date}' and requires a non-empty input. It is an internal invariant violation, not a user-facing HTTP error, and typically surfaces as a 500 unless caught upstream.

Source

Thrown at routes/calendar_routes.py:141


def _safe_ics_filename(name: str) -> str:
    """Return a conservative .ics filename safe for Content-Disposition."""
    stem = name if isinstance(name, str) else ""
    stem = re.sub(r"[^A-Za-z0-9._-]", "_", stem).strip("._-")
    if not stem:
        stem = "calendar"
    return f"{stem[:128]}.ics"


def _resolve_base_uid(uid: str) -> str:
    """Extract the base series UID from a compound occurrence UID.

    Compound UIDs have the form ``{base_uid}::{date_suffix}``.
    For plain UIDs (no ``::``), returns the UID unchanged.
    """
    if not uid:
        raise ValueError("empty uid")
    idx = uid.find("::")
    if idx == -1:
        return uid       # plain UID — no suffix
    base = uid[:idx]
    if not base:
        raise ValueError("malformed compound UID: missing base before ::")
    return base


async def _push_caldav_event_after_commit(owner: str, uid: str, action: str):
    """Best-effort CalDAV write-through. Local writes stay authoritative if
    the remote server is unreachable; pending flags let /sync retry later."""
    try:
        result = {"ok": True}
        if action == "create":
            from src.caldav_sync import push_event_create
            result = await push_event_create(owner, uid)
        elif action == "update":

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Generate a uid before saving events (uuid4 or the app's uid generator) so the helper never sees an empty string.
  2. Validate uid presence at the route boundary and return 400 before reaching recurrence logic.
  3. If seen in logs as 500, trace the caller passing the empty uid.

Example fix

# before
event = {"uid": body.get("uid", ""), "title": ...}

# after
import uuid
event = {"uid": (body.get("uid") or str(uuid.uuid4())).strip(), "title": ...}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!uid || !uid.trim()) return badRequest('uid is required');

Type guard

const hasUid = (e: {uid?: unknown}): e is {uid: string} =>
  typeof e.uid === 'string' && e.uid.trim().length > 0;

Try / catch

try { base = _resolveBaseUid(uid); } catch (e) { /* ValueError: reject payload with 400, don't 500 */ }

Prevention

When it happens

Trigger: Calling _resolve_base_uid('') — e.g. an event payload with uid: '' or a missing uid field flowing into recurrence handling; programmatic callers passing None.

Common situations: Client submits an event without generating a uid; a sync/import path leaves uid empty; malformed CalDAV data with empty UID components.

Related errors


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