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
- Inspect the HTTP status of the failing request (network tab or by extending the thrown error with res.status).
- Ensure the API backend and its edge routing are running if the status is 5xx.
- Refresh authentication if 401/403.
- Verify the character still exists (fetchRemoteById) before bookmarking.
- 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
- Treat bookmark and like as one auth/availability domain: if one fails, expect the other to.
- Refresh bookmarks from the server after recovery instead of trusting local toggle state.
- Bound retries; do not auto-retry non-idempotent-looking failures in a loop.
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
- Failed to like character
- Failed to fetch providers
- Failed to add provider
- Failed to remove provider
- Failed to update provider config
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)