tonhowtf/omniget · warning

notebooksStore.refresh failed

Error message

notebooksStore.refresh failed

What it means

notebooksStore.refresh() reloads the notebook list via notesNotebooksList({includeClosed:true}), a Tauri pluginInvoke of 'study:notes:notebooks:list'. On rejection the catch logs 'notebooksStore.refresh failed' and leaves this.list stale. All mutating store actions (create, rename, close, reopen, delete, movePage) call refresh afterwards, so a failed refresh shows outdated notebook state even when the mutation itself succeeded.

Solutions

  1. Inspect the logged rejection reason from notesNotebooksList to identify the backend error.
  2. Verify 'study:notes:notebooks:list' exists and is allowed in Tauri capabilities.
  3. Test the bridge call directly from the console to isolate store vs backend.
  4. If the DB is locked or corrupted, resolve the writer contention or repair the notes DB.
  5. Make UI affordances call refresh() again (retry) since the swallowed error leaves this.list stale.

Example fix

// before
async refresh() {
  try {
    this.list = await notesNotebooksList({ includeClosed: true });
  } catch (e) {
    console.warn("notebooksStore.refresh failed", e);
  }
}
// after
async refresh() {
  try {
    this.list = await notesNotebooksList({ includeClosed: true });
  } catch (e) {
    console.warn("notebooksStore.refresh failed", e);
    this.refreshFailed = true; // surface staleness to the UI for retry
  }
}
Defensive patterns

Strategy: retry

Validate before calling

// guard before treating list as authoritative
if (!notebooksStore.hydrated) await notebooksStore.hydrate();
const count = notebooksStore.list.length;

Type guard

function hasErrorReason(e: unknown): e is Error {
  return e instanceof Error;
}

Try / catch

let lastErr: unknown;
for (let i = 0; i < 3; i++) {
  await notebooksStore.refresh();
  if (notebooksStore.list.length > 0) break;
  lastErr = new Error('refresh returned empty');
  await new Promise((r) => setTimeout(r, 300 * (i + 1)));
}

Prevention

When it happens

Trigger: notesNotebooksList rejects: backend command error, notes DB unavailable, plugin/capability misconfiguration, or IPC failure; it runs after every successful mutation, so it also fires when only the follow-up list query fails.

Common situations: Backend list endpoint broken after a schema migration; DB locked by another writer; command renamed on the backend but not the bridge; running outside the Tauri shell so pluginInvoke rejects; transient IPC failure right after a mutation.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/ff3e659100b079ce. Report an issue: GitHub.

Appendix: source

Thrown at src/lib/study-notes/notebooks-store.svelte.ts:78

      const [rows, active] = await Promise.all([
        notesNotebooksList({ includeClosed: true }),
        notesNotebooksActiveGet(),
      ]);
      this.list = rows;
      this.activeId = active.notebook_id;
    } catch (e) {
      console.warn("notebooksStore.hydrate failed", e);
    } finally {
      this.hydrated = true;
      this.loading = false;
    }
  }

  async refresh() {
    try {
      this.list = await notesNotebooksList({ includeClosed: true });
    } catch (e) {
      console.warn("notebooksStore.refresh failed", e);
    }
  }

  pageCountOf(id: number): number {
    return this.list.find((n) => n.id === id)?.page_count ?? 0;
  }

  byId(id: number): Notebook | null {
    return this.list.find((n) => n.id === id) ?? null;
  }

  async setActive(notebookId: number) {
    if (this.activeId === notebookId) return;
    this.activeId = notebookId;
    try {
      await notesNotebooksActiveSet(notebookId);
    } catch (e) {
      console.warn("setActive failed", e);

View on GitHub (pinned to 8600b91f42)