{"record":{"id":"0fbbf9d219002f12","repo":"moeru-ai/airi","slug":"failed-to-bookmark-character","errorCode":null,"errorMessage":"Failed to bookmark character","messagePattern":"Failed to bookmark character","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/services/characters.ts","lineNumber":229,"sourceCode":"    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 {\n    buildLocal,\n    fetchRemote,\n    fetchRemoteById,\n    createRemote,\n    updateRemote,\n    removeRemote,\n    likeRemote,\n    bookmarkRemote,\n  }\n}\n","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui/src/services/characters.ts#L211-L247","documentation":"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.","triggerScenarios":"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).","commonSituations":"Bookmarking a deleted character; expired session; double-bookmark race; permission mismatch.","solutions":["Handle 404 by removing the stale character from the UI.","Re-authenticate on 401/403 before retrying.","On 409/already-bookmarked, reconcile local state to 'bookmarked'.","Include res.status for status-based handling."],"exampleFix":"// before\nif (!res.ok)\n  throw new Error('Failed to bookmark character')\n\n// after\nif (!res.ok) {\n  if (res.status === 409)\n    return parse(await res.json().catch(() => ({})))\n  throw new Error(`Failed to bookmark 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 bookmarkRemote(client, id)","typeGuard":null,"tryCatchPattern":"try {\n  return await bookmarkRemote(client, id)\n}\ncatch (error) {\n  // Patch service to expose status; on 409 reconcile to 'bookmarked', on 404 remove locally, on 401/403 re-auth.\n  throw error\n}","preventionTips":["Reconcile local state to 'bookmarked' on 409 (already bookmarked).","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"}