moeru-ai/airi · error · Error
Failed to remove character
Error message
Failed to remove character
What it means
Thrown by CharactersService.removeRemote when DELETE /v1/characters/:id returns non-ok. Unlike the other operations, removeRemote does not parse JSON on success (the typed return is { ok: boolean }); it only checks ok and re-checks the abort signal. The message discards the status.
Source
Thrown at packages/stage-ui/src/services/characters.ts:210
async function updateRemote(client: CharactersRemoteClient, id: string, payload: UpdateCharacterPayload, options?: CharacterServiceOptions): Promise<Character> {
options?.abortSignal?.throwIfAborted()
const res = await client.api.v1.characters[':id'].$patch({
param: { id },
json: payload,
}, requestOptions(options))
if (!res.ok)
throw new Error('Failed to update character')
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)View on GitHub (pinned to 27111382b4)
Solutions
- Treat 404 on delete as success if the local copy is already gone (idempotent delete).
- Re-authenticate on 401/403 before retrying.
- Confirm ownership before showing the delete control.
- Include res.status in the message to allow 404-tolerant handling upstream.
Example fix
// before
if (!res.ok)
throw new Error('Failed to remove character')
// after: tolerate already-deleted
if (!res.ok && res.status !== 404)
throw new Error(`Failed to remove character ${id} (status ${res.status})`) Defensive patterns
Strategy: try-catch
Validate before calling
// Deletion is idempotent; nothing required beyond a valid id and auth.
function isValidCharacterId(id: string): boolean {
return typeof id === 'string' && id.length > 0
} Try / catch
try {
await removeRemote(client, id)
}
catch (error) {
// Patch service to expose status; tolerate 404 since the character is already gone.
// On 401/403 re-authenticate and retry once.
throw error
} Prevention
- Treat 404 on delete as success when the local copy is already removed (idempotent).
- Embed res.status in the service error so callers can tolerate 404.
- Guard against double-delete by disabling the control after the first click.
When it happens
Trigger: client.api.v1.characters[':id'].$delete({ param: { id } }) resolves with ok=false. Typical: 401/403 (not owner / not authenticated), 404 (already deleted), 500 (server). Optimistic UI may have already removed the local copy, masking a server-side failure.
Common situations: Double delete (user clicks delete twice, or the character was already gone); permission mismatch; session expired; transient server error during deletion.
Related errors
- Failed to fetch character
- Failed to fetch characters
- Failed to create character
- Failed to update character
- Failed to like character
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/a475bf4e2c121513.
Report an issue: GitHub.