odysseus-dev/odysseus · warning · HTTPException
Note has no checklist items
Error message
Note has no checklist items
What it means
400 raised at routes/note/note_routes.py:830 in POST /notes/{note_id}/items/{index}/toggle when the note exists (and is owned by the caller) but `note.items` is falsy — null, empty string, or an empty JSON payload. The route toggles checklist items, so there is nothing to toggle. Note the next guard: index out of range is a separate 400, and a non-JSON items column would raise json.JSONDecodeError instead.
Source
Thrown at routes/note/note_routes.py:830
return {"ok": True, "archived": note.archived}
finally:
db.close()
# --- TOGGLE CHECKLIST ITEM ---
@router.post("/{note_id}/items/{index}/toggle")
def toggle_item(request: Request, note_id: str, index: int):
user = _owner(request)
db = SessionLocal()
try:
note = db.query(Note).filter(Note.id == note_id).first()
if not note:
raise HTTPException(404, "Note not found")
# SECURITY: strict ownership — previously `note.owner and note.owner != user`
# let any user touch a row whose owner field was null/empty.
if user is not None and note.owner != user:
raise HTTPException(404, "Note not found")
if not note.items:
raise HTTPException(400, "Note has no checklist items")
items = json.loads(note.items)
if index < 0 or index >= len(items):
raise HTTPException(400, f"Item index {index} out of range")
items[index]["done"] = not items[index].get("done", False)
note.items = json.dumps(items)
flag_modified(note, "items")
db.commit()
return {"ok": True, "items": items}
finally:
db.close()
# --- FIRE REMINDER ---
@router.post("/fire-reminder")
async def fire_reminder(request: Request):
"""Dispatch a reminder according to user settings.
Called by the frontend when a reminder fires. Optionally generates an
LLM synthesis line and/or sends an email through configured SMTP.View on GitHub (pinned to f9235ebbf1)
Solutions
- Only show/offer the toggle when the note actually has checklist items (non-empty items array).
- If the checklist was cleared by mistake, re-add items via PUT ({"items": [{"text": "...", "done": false}]}) then toggle.
- Client-side, guard: if (!note.items?.length) skip/disable the toggle.
- Handle races by re-fetching the note on 400 and reconciling UI state.
Example fix
// before
<button onClick={() => api.toggleItem(note.id, i)} />
// after
{(note.items?.length ?? 0) > 0
? <button onClick={() => api.toggleItem(note.id, i)} />
: null} Defensive patterns
Strategy: type-guard
Validate before calling
const items = (() => { try { return JSON.parse(note.items ?? 'null'); } catch { return null; } })();
if (!Array.isArray(items) || items.length === 0) { disableToggle(); return; }
if (index < 0 || index >= items.length) { disableToggle(); return; } Type guard
interface ChecklistItem { text?: string; done?: boolean }
function isChecklistNote(n: Note): n is Note & { items: ChecklistItem[] } {
try { const v = JSON.parse(n.items ?? 'null'); return Array.isArray(v) && v.length > 0; }
catch { return false; }
} Try / catch
try { await api.toggleItem(noteId, index); }
catch (e) {
if (e.status === 400 && /checklist/i.test(e.message)) { hideChecklistUI(noteId); return; }
throw e;
} Prevention
- Render toggle controls only when the note has a non-empty items array.
- When clearing items via PUT, also tear down any live checklist listeners.
- On 400, re-fetch the note and reconcile UI before any retry.
When it happens
Trigger: Calling the toggle on a plain text/image note that was never given a checklist. Toggling an item after the note's items were cleared client-side (items set to null/[] via PUT). Race where a PUT removes items while a checklist toggle is in flight.
Common situations: UI rendering a toggle control for notes without checklists (missing conditional on note type). Converting a checklist note to a text note then clicking a stale toggle. Scripts assuming every note has items.
Related errors
- Current password is incorrect
- {key} must be an integer
- Password is required
- Invalid recurring occurrence uid
- Invalid ICS file: {e}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/798ff9c24dfeb69e.
Report an issue: GitHub.