{"record":{"id":"ef89a6f62202750c","repo":"moeru-ai/airi","slug":"failed-to-like-character","errorCode":null,"errorMessage":"Failed to like character","messagePattern":"Failed to like character","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/services/characters.ts","lineNumber":218,"sourceCode":"\n    const data = await res.json()\n    options?.abortSignal?.throwIfAborted()\n    return parse(data)\n  }\n\n  async function removeRemote(client: CharactersRemoteClient, id: string, options?: CharacterServiceOptions): Promise<void> {\n    options?.abortSignal?.throwIfAborted()\n    const res = await client.api.v1.characters[':id'].$delete({ param: { id } }, requestOptions(options))\n    if (!res.ok)\n      throw new Error('Failed to remove character')\n    options?.abortSignal?.throwIfAborted()\n  }\n\n  async function likeRemote(client: CharactersRemoteClient, id: string, options?: CharacterServiceOptions): Promise<Character> {\n    options?.abortSignal?.throwIfAborted()\n    const res = await client.api.v1.characters[':id'].like.$post({ param: { id } }, requestOptions(options))\n    if (!res.ok)\n      throw new Error('Failed to like character')\n\n    const data = await res.json()\n    options?.abortSignal?.throwIfAborted()\n    return parse(data)\n  }\n\n  async function bookmarkRemote(client: CharactersRemoteClient, id: string, options?: CharacterServiceOptions): Promise<Character> {\n    options?.abortSignal?.throwIfAborted()\n    const res = await client.api.v1.characters[':id'].bookmark.$post({ param: { id } }, requestOptions(options))\n    if (!res.ok)\n      throw new Error('Failed to bookmark character')\n\n    const data = await res.json()\n    options?.abortSignal?.throwIfAborted()\n    return parse(data)\n  }\n\n  return {","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/services/characters.ts#L200-L236","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Temporarily log res.status (or extend the service error to include it) to identify the exact HTTP code before guessing.","Verify the hosted backend stack is up and reachable (server/docker-compose.yaml, server/dev/caddy routing) if status is 5xx/502.","Re-authenticate or refresh the auth session if the status is 401/403.","Confirm the id exists on the same environment with fetchRemoteById before liking.","Disable the like button while a request is in flight and pass an AbortSignal to avoid duplicate/stale races."],"exampleFix":"// before\nconst res = await client.api.v1.characters[':id'].like.$post({ param: { id } }, requestOptions(options))\nif (!res.ok)\n  throw new Error('Failed to like character')\n\n// after\nconst res = await client.api.v1.characters[':id'].like.$post({ param: { id } }, requestOptions(options))\nif (!res.ok)\n  throw new Error(`Failed to like character (HTTP ${res.status})`)","handlingStrategy":"try-catch","validationCode":"// Before liking, confirm the character exists on the remote and the session is live\nconst character = await charactersService.fetchRemoteById(client, id, { abortSignal }).catch(() => null)\nif (!character)\n  throw new Error(`Character ${id} does not exist on this server`)\nawait charactersService.likeRemote(client, id, { abortSignal })","typeGuard":null,"tryCatchPattern":"try {\n  const updated = await charactersService.likeRemote(client, id, { abortSignal })\n}\ncatch (error) {\n  if (abortSignal?.aborted) return // user cancelled, not a failure\n  // HTTP-layer failure: surface, optionally prompt re-login on 401\n  toast.error(errorMessageFrom(error) ?? 'Failed to like character')\n}","preventionTips":["Pass an AbortSignal and cancel in-flight like requests when the view unmounts.","Verify remote character ids after switching environments (local vs hosted).","Keep auth token refresh running so requests do not silently 401.","Disable the like button while a request is in flight to avoid duplicate posts."],"tags":["http","api-client","characters","remote","hono-rpc"],"backgroundTag":"http-error-status","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}