odysseus-dev/odysseus · error · Error

Failed to save note

Error message

Failed to save note

What it means

Thrown by _saveNote in notes.js when PUT /api/notes/{id} or POST /api/notes returns non-ok. Unlike its siblings it discards the response body entirely, giving a fixed message with no status or server detail. Callers use it to decide whether to toast a save failure.

Source

Thrown at static/js/notes.js:471

    const data = await res.json();
    _notes = data.notes || data || [];
  } catch (e) {
    console.error('Failed to fetch notes:', e);
    _notes = [];
  } finally {
    _loading = false;
  }
}

async function _saveNote(note) {
  const method = note.id ? 'PUT' : 'POST';
  const url = note.id ? `${API_BASE}/api/notes/${note.id}` : `${API_BASE}/api/notes`;
  const res = await fetch(url, {
    method, credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(note),
  });
  if (!res.ok) throw new Error('Failed to save note');
  return await res.json();
}

async function _deleteNoteApi(id) {
  // v2 review — used to swallow 4xx/5xx silently. Throw so callers can
  // distinguish success vs failure and toast accordingly.
  const r = await fetch(`${API_BASE}/api/notes/${id}`, { method: 'DELETE', credentials: 'same-origin' });
  if (!r.ok) throw new Error('HTTP ' + r.status);
}

async function _patchNote(id, patch) {
  const res = await fetch(`${API_BASE}/api/notes/${id}`, {
    method: 'PUT', credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(patch),
  });
  if (!res.ok) throw new Error('Failed to update note');
  return await res.json();

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Reload the notes list to resync ids, then re-apply the edit and save.
  2. If validation, check required fields (title/body/types) against the note schema.
  3. Include the status code in the message (see exampleFix) to make recurrences diagnosable.
  4. Re-authenticate if the session expired.

Example fix

// before
if (!res.ok) throw new Error('Failed to save note');

// after
if (!res.ok) throw new Error(`Failed to save note (HTTP ${res.status})`);
Defensive patterns

Strategy: try-catch

Validate before calling

if (note.id && !/^[a-z0-9-]+$/i.test(String(note.id))) note.id = undefined; // force create when id looks stale

Type guard

const isNotePayload = (n) => n && typeof n.body === 'string';

Try / catch

try { return await _saveNote(note); } catch (e) { if (/HTTP 40[04]/.test(e.message)) { delete note.id; return await _saveNote(note); } throw e; }

Prevention

When it happens

Trigger: Saving a note whose id no longer exists server-side (404, e.g. cleared store or another device deleted it); sending a body that fails note schema validation (400); auth cookie expired (401).

Common situations: Notes stored in a file/database wiped between sessions while the UI still held stale ids; concurrent editors; server restarted with a fresh data store.

Related errors


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