yikart/AiToEarn · error · Error
Save failed
Error message
Save failed
What it means
In ChatDetailPage, saving a renamed task calls the update-title API and expects result.code === 0. When the response code is non-zero (or result is falsy), it throws Error('Save failed') after skipping the local title update.
Source
Thrown at project/aitoearn-web/src/app/[lng]/chat/[taskId]/page.tsx:322
catch {
setIsFavorited(!newValue) // 失败回滚
toast.error(t('message.error'))
}
}, [isFavorited, taskId, t])
/**
* 保存标题 - 乐观更新
*/
const handleSaveTitle = useCallback(
async (newTitle: string) => {
const result = await agentApi.updateTaskTitle(taskId, newTitle)
if (result && result.code === 0) {
// 更新本地 task 状态,触发页面标题和 header 标题更新
updateTaskTitle(newTitle)
toast.success(t('task.titleUpdated'))
}
else {
throw new Error('Save failed')
}
},
[taskId, t, updateTaskTitle],
)
// 加载中状态(仅非活跃任务显示骨架屏)
if (isLoading && !isActiveTask) {
return <ChatLoadingSkeleton />
}
return (
<div className="flex flex-col h-full">
{/* 顶部导航 */}
<ChatHeader
title={task?.title}
defaultTitle={t('task.newChat')}
isGenerating={isGenerating}
progress={progress}View on GitHub (pinned to d3aa8bea5b)
Solutions
- Log the full result object (code + message) to see the backend's business error reason
- Refresh/re-fetch the task to confirm taskId is still valid, then retry
- Re-authenticate if the error envelope indicates auth failure (401-style code with HTTP 200)
- Validate the new title (non-empty, length limits) before calling the API and surface the API's message instead of the generic 'Save failed'
Example fix
// before
else {
throw new Error('Save failed')
}
// after
else {
throw new Error(`Save failed: ${result?.message ?? 'unknown error'} (code: ${result?.code})`)
} Defensive patterns
Strategy: try-catch
Validate before calling
const newTitle = title.trim()
if (!newTitle || newTitle.length > 100) { toast.error('标题不能为空且需小于100字'); return }
if (!taskId) return Type guard
function isApiSuccess(result: unknown): result is { code: 0; data?: unknown } {
return typeof result === 'object' && result !== null && (result as any).code === 0
} Try / catch
try {
await saveTitle(newTitle)
} catch (e) {
if (e.message === 'Save failed') {
toast.error('保存失败,请刷新页面后重试')
await refetchTask(taskId) // resync local state with server
return
}
throw e
} Prevention
- Include the API's code/message in the thrown error for diagnosability
- Re-validate taskId against the server before mutations (task may be deleted elsewhere)
- Handle auth-expiry globally so error envelopes with HTTP 200 trigger re-login
- Trim/validate title input before sending
When it happens
Trigger: The rename-task API returns { code: <non-zero> } (business error: invalid taskId, permission denied, validation failure) or returns no parsable result — even when the HTTP status was 200.
Common situations: Task already deleted in another tab (stale taskId), session/token expired so the backend returns an error envelope with HTTP 200, empty or invalid new title rejected by validation, or network layer returning an undefined result.
Related errors
- body.code
- AiCallFailed
- ChannelPlatformApiFailed
- 解析后的数据为空
- 解析响应数据失败: ${err instanceof Error ? err.message : String(err)
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/adb3e18aa7cb38f6.
Report an issue: GitHub.