moeru-ai/airi · error · Error

Failed to bookmark character

Error message

Failed to bookmark character

What it means

Thrown by bookmarkRemote when POST /api/v1/characters/:id/bookmark returns a non-2xx status through the typed RPC client. Like the other character service methods it only inspects res.ok, so authentication problems, missing characters, and backend failures are indistinguishable from the message alone. It fires on the bookmark toggle path, typically from a character card action button.

Solutions

  1. Inspect the HTTP status of the failing request (network tab or by extending the thrown error with res.status).
  2. Ensure the API backend and its edge routing are running if the status is 5xx.
  3. Refresh authentication if 401/403.
  4. Verify the character still exists (fetchRemoteById) before bookmarking.
  5. Guard the UI toggle with an in-flight flag and abort signal.

Example fix

// before
const res = await client.api.v1.characters[':id'].bookmark.$post({ param: { id } }, requestOptions(options))
if (!res.ok)
  throw new Error('Failed to bookmark character')

// after
const res = await client.api.v1.characters[':id'].bookmark.$post({ param: { id } }, requestOptions(options))
if (!res.ok)
  throw new Error(`Failed to bookmark character (HTTP ${res.status})`)
Defensive patterns

Strategy: try-catch

Validate before calling

const character = await charactersService.fetchRemoteById(client, id, { abortSignal }).catch(() => null)
if (!character)
  throw new Error(`Character ${id} does not exist on this server`)
await charactersService.bookmarkRemote(client, id, { abortSignal })

Try / catch

try {
  await charactersService.bookmarkRemote(client, id, { abortSignal })
}
catch (error) {
  if (abortSignal?.aborted) return
  toast.error(errorMessageFrom(error) ?? 'Failed to bookmark character')
  // on auth suspicion (401), route the user to sign-in
}

Prevention

When it happens

Trigger: Bookmarking a character whose id is unknown to the hosted API (404); bookmarking with an expired or missing auth session (401/403); API server error (500); request racing a character removal. Happens on the same client used by likeRemote, so any environment where likes also fail will fail bookmarks too.

Common situations: Remote client pointed at a backend that is stopped or misrouted; stale character id kept in UI state after the character was deleted in another tab/window; token expiry during a long-lived session.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/0fbbf9d219002f12. Report an issue: GitHub.

Appendix: source

Thrown at packages/stage-ui/src/services/characters.ts:229

    options?.abortSignal?.throwIfAborted()
  }

  async function likeRemote(client: CharactersRemoteClient, id: string, options?: CharacterServiceOptions): Promise<Character> {
    options?.abortSignal?.throwIfAborted()
    const res = await client.api.v1.characters[':id'].like.$post({ param: { id } }, requestOptions(options))
    if (!res.ok)
      throw new Error('Failed to like character')

    const data = await res.json()
    options?.abortSignal?.throwIfAborted()
    return parse(data)
  }

  async function bookmarkRemote(client: CharactersRemoteClient, id: string, options?: CharacterServiceOptions): Promise<Character> {
    options?.abortSignal?.throwIfAborted()
    const res = await client.api.v1.characters[':id'].bookmark.$post({ param: { id } }, requestOptions(options))
    if (!res.ok)
      throw new Error('Failed to bookmark character')

    const data = await res.json()
    options?.abortSignal?.throwIfAborted()
    return parse(data)
  }

  return {
    buildLocal,
    fetchRemote,
    fetchRemoteById,
    createRemote,
    updateRemote,
    removeRemote,
    likeRemote,
    bookmarkRemote,
  }
}

View on GitHub (pinned to 677329427f)