odysseus-dev/odysseus · warning · HTTPException

Account not found

Error message

Account not found

What it means

Raised as HTTP 404 by PUT /config/accounts/{account_id} when no account in the caller's saved CalDAV account list has an id equal to the path parameter. Accounts are stored per-owner and looked up by exact string match on the 'id' field (a UUID assigned at creation), so the error means either the id is wrong, was deleted, or belongs to a different user.

Source

Thrown at routes/calendar_routes.py:953

            "password": encrypt(body["password"]),
        }
        accounts = _get_caldav_accounts(owner)
        accounts.append(new_acc)
        _save_caldav_accounts(owner, accounts)
        return {"ok": True, "id": new_acc["id"]}

    @router.put("/config/accounts/{account_id}")
    async def update_caldav_account(account_id: str, request: Request):
        """Update an existing CalDAV account by id."""
        owner = _require_user(request)
        try:
            body = await request.json()
        except Exception:
            body = {}
        accounts = _get_caldav_accounts(owner)
        idx = next((i for i, a in enumerate(accounts) if a.get("id") == account_id), None)
        if idx is None:
            raise HTTPException(404, "Account not found")
        acc = dict(accounts[idx])
        if body.get("url"):
            from src.caldav_sync import validate_caldav_url
            try:
                acc["url"] = validate_caldav_url(body["url"])
            except ValueError as e:
                raise HTTPException(400, str(e))
        if body.get("label") is not None:
            acc["label"] = (body.get("label") or "").strip() or "CalDAV"
        if body.get("username") is not None:
            acc["username"] = (body.get("username") or "").strip()
        if body.get("password"):
            from src.secret_storage import encrypt
            acc["password"] = encrypt(body["password"])
        accounts[idx] = acc
        _save_caldav_accounts(owner, accounts)
        return {"ok": True}

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Re-fetch the account list (GET /config/accounts) and use the current id from that response for the PUT.
  2. If the account is genuinely gone, recreate it with POST /config/accounts instead of updating.
  3. Confirm the account belongs to the authenticated user — ids from another owner always 404 here.

Example fix

// before
await fetch(`/config/accounts/${staleAccountId}`, {method: 'PUT', ...});

// after
const accounts = await (await fetch('/config/accounts')).json();
const acc = accounts.accounts.find(a => a.id === staleAccountId);
if (acc) {
  await fetch(`/config/accounts/${acc.id}`, {method: 'PUT', ...});
} else {
  // account was deleted elsewhere — recreate or refresh UI
}
Defensive patterns

Strategy: validation

Validate before calling

const accounts = await (await fetch('/config/accounts')).json();
const exists = accounts.accounts.some(a => a.id === targetId);
if (!exists) { refreshAccountList(); return; }
await fetch(`/config/accounts/${targetId}`, {method: 'PUT', ...});

Try / catch

try { await api.put(`/config/accounts/${id}`, patch); }
catch (e) {
  if (e.status === 404) { await reloadAccounts(); /* re-sync ids */ }
  else throw e;
}

Prevention

When it happens

Trigger: PUT /config/accounts/{id} with an id copied from a different owner's account; using an id after that account was deleted via DELETE /config/accounts/{id}; a truncated or typo'd UUID in the path; stale client state holding ids from before the account store was reset.

Common situations: UI list of accounts not refreshed after a delete, so the next edit targets a dead id; two browser tabs or devices with divergent account lists; the accounts file for this owner was recreated (ids regenerated) while the client cached old ids; case/format mismatch in the UUID string.

Related errors


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