moeru-ai/airi · error · Error

Failed to bookmark character

Error message

Failed to bookmark character

What it means

Thrown by CharactersService.bookmarkRemote when POST /v1/characters/:id/bookmark returns non-ok. Symmetric to likeRemote: expects a parseable character back; aborts on failure. Message discards status.

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 27111382b4)

Solutions

  1. Handle 404 by removing the stale character from the UI.
  2. Re-authenticate on 401/403 before retrying.
  3. On 409/already-bookmarked, reconcile local state to 'bookmarked'.
  4. Include res.status for status-based handling.

Example fix

// before
if (!res.ok)
  throw new Error('Failed to bookmark character')

// after
if (!res.ok) {
  if (res.status === 409)
    return parse(await res.json().catch(() => ({})))
  throw new Error(`Failed to bookmark character ${id} (status ${res.status})`)
}
Defensive patterns

Strategy: try-catch

Validate before calling

function isValidCharacterId(id: string): boolean {
  return typeof id === 'string' && id.length > 0
}

if (!isValidCharacterId(id))
  throw new Error(`Invalid character id: ${id}`)
await bookmarkRemote(client, id)

Try / catch

try {
  return await bookmarkRemote(client, id)
}
catch (error) {
  // Patch service to expose status; on 409 reconcile to 'bookmarked', on 404 remove locally, on 401/403 re-auth.
  throw error
}

Prevention

When it happens

Trigger: client.api.v1.characters[':id'].bookmark.$post({ param: { id } }) resolves with ok=false. Typical: 401/403 (not authenticated), 404 (character deleted), 409 (already bookmarked), 500 (server).

Common situations: Bookmarking a deleted character; expired session; double-bookmark race; permission mismatch.

Related errors


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