odysseus-dev/odysseus · error · HTTPException

Note not found

Error message

Note not found

What it means

404 raised at routes/note/note_routes.py:695 in GET /notes/{note_id} when no Note row has that id. Distinct from the ownership 404 on the next line: this one means the id does not exist at all, for any user. The handler intentionally returns 404 (not 403) in both cases to avoid leaking which ids exist.

Source

Thrown at routes/note/note_routes.py:695

                repeat=body.repeat or "none",
                sort_order=body.sort_order if body.sort_order is not None else 0,
            )
            db.add(note)
            db.commit()
            db.refresh(note)
            return _note_to_dict(note)
        finally:
            db.close()

    # --- GET ONE ---
    @router.get("/{note_id}")
    def get_note(request: Request, note_id: str):
        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")
            return _note_to_dict(note)
        finally:
            db.close()

    # --- UPDATE ---
    @router.put("/{note_id}")
    def update_note(request: Request, note_id: str, body: NoteUpdate):
        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`

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Re-fetch the note list; if the id is gone, remove it from local state/URL.
  2. If it should exist, confirm you are pointed at the right environment/DB.
  3. Check the id is complete (not truncated by URL handling).
  4. Treat as terminal for that id — do not retry.

Example fix

// before
const note = await api.getNote(id);  // throws on 404

// after
const res = await fetch(`/notes/${id}`);
if (res.status === 404) { router.replace('/notes'); return null; }
const note = await res.json();
Defensive patterns

Strategy: try-catch

Validate before calling

const ids = new Set((await api.listNotes()).map(n => n.id));
if (!ids.has(noteId)) { navigate('/notes'); }

Try / catch

try { note = await api.getNote(noteId); }
catch (e) { if (e.status === 404) { router.replace('/notes'); return null; } throw e; }

Prevention

When it happens

Trigger: Fetching a note deleted by the same user in another tab/device, a stale share/bookmark link after deletion, or a malformed/guessed note id. Also a note id from a different server/DB after a restore or environment switch.

Common situations: Bookmarked note URLs surviving deletion. Frontend list cache showing a note already removed. Split-brain between two app instances on different DBs.

Related errors


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