{"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/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui/src/services/characters.ts#L200-L236","documentation":"Thrown by CharactersService.likeRemote when POST /v1/characters/:id/like returns non-ok. The service expects a parseable character back (the liked state updated). Failure aborts parsing, so the like is not reflected.","triggerScenarios":"client.api.v1.characters[':id'].like.$post({ param: { id } }) resolves with ok=false. Typical: 401/403 (not authenticated), 404 (character deleted), 409 (already liked / like constraint), 500 (server).","commonSituations":"Liking a character that was just deleted; session expired; double-like race where the backend rejects the second; permission to view but not interact.","solutions":["Handle 404 by refreshing the list and removing the stale character from the UI.","Re-authenticate on 401/403 and retry the like once.","On 409/already-liked, reconcile local state to 'liked' rather than erroring.","Include res.status to enable status-based reconciliation."],"exampleFix":"// before\nif (!res.ok)\n  throw new Error('Failed to like character')\n\n// after\nif (!res.ok) {\n  if (res.status === 409) {\n    // already liked; treat as success\n    return parse(await res.json().catch(() => ({})))\n  }\n  throw new Error(`Failed to like character ${id} (status ${res.status})`)\n}","handlingStrategy":"try-catch","validationCode":"function isValidCharacterId(id: string): boolean {\n  return typeof id === 'string' && id.length > 0\n}\n\nif (!isValidCharacterId(id))\n  throw new Error(`Invalid character id: ${id}`)\nawait likeRemote(client, id)","typeGuard":null,"tryCatchPattern":"try {\n  return await likeRemote(client, id)\n}\ncatch (error) {\n  // Patch service to expose status; on 409 reconcile to 'liked', on 404 remove locally, on 401/403 re-auth.\n  throw error\n}","preventionTips":["Reconcile local state to 'liked' on 409 (already liked) instead of erroring.","Embed res.status in the service error for status-based handling.","Handle 404 by removing the stale character from the UI."],"tags":["network","http","characters","api","auth"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}