moeru-ai/airi · error · Error

Failed to like character

Error message

Failed to like character

What it means

Thrown by likeRemote in the stage-ui characters service when POST /api/v1/characters/:id/like (issued through the typed Hono RPC client client.api.v1.characters[':id'].like.$post) resolves with a non-2xx status. Every HTTP failure collapses into this one message because the service only checks res.ok, so 401/403 auth failures, 404 unknown character ids, and 500 backend errors all look identical. The real status code is on the response object and is not propagated into the error.

Solutions

  1. Temporarily log res.status (or extend the service error to include it) to identify the exact HTTP code before guessing.
  2. Verify the hosted backend stack is up and reachable (server/docker-compose.yaml, server/dev/caddy routing) if status is 5xx/502.
  3. Re-authenticate or refresh the auth session if the status is 401/403.
  4. Confirm the id exists on the same environment with fetchRemoteById before liking.
  5. Disable the like button while a request is in flight and pass an AbortSignal to avoid duplicate/stale races.

Example fix

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

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

Strategy: try-catch

Validate before calling

// Before liking, confirm the character exists on the remote and the session is live
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.likeRemote(client, id, { abortSignal })

Try / catch

try {
  const updated = await charactersService.likeRemote(client, id, { abortSignal })
}
catch (error) {
  if (abortSignal?.aborted) return // user cancelled, not a failure
  // HTTP-layer failure: surface, optionally prompt re-login on 401
  toast.error(errorMessageFrom(error) ?? 'Failed to like character')
}

Prevention

When it happens

Trigger: Calling like on a character service bound to a remote client while the auth session is expired (401), the character id does not exist on that server (404, e.g. an id from another environment), the hosted API returns 500, or the request races a character deletion. Also triggered when the server/docker-compose backend or the Caddy edge route for /api/v1 is down.

Common situations: Local docker-compose backend not running while the app points at the remote client; Better Auth/OIDC token expired; liking from a stale list view after the character was removed elsewhere; gateway/proxy returning 502 in front of server/apps/api.

Related errors


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

Appendix: source

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

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

  async function removeRemote(client: CharactersRemoteClient, id: string, options?: CharacterServiceOptions): Promise<void> {
    options?.abortSignal?.throwIfAborted()
    const res = await client.api.v1.characters[':id'].$delete({ param: { id } }, requestOptions(options))
    if (!res.ok)
      throw new Error('Failed to remove character')
    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 {

View on GitHub (pinned to 677329427f)