{"record":{"id":"4a7bf32a6a8cad25","repo":"bytedance/deer-flow","slug":"failed-to-update-conversation","errorCode":null,"errorMessage":"Failed to update conversation.","messagePattern":"Failed to update conversation\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/core/threads/api.ts","lineNumber":129,"sourceCode":"}\n\nexport async function patchThreadMetadata(\n  threadId: string,\n  metadata: ThreadMetadataPatch,\n): Promise<ThreadMetadataPatchResponse> {\n  const response = await fetchWithAuth(\n    `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}`,\n    {\n      method: \"PATCH\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n      body: JSON.stringify({ metadata }),\n    },\n  );\n\n  if (!response.ok) {\n    throw new Error(\n      await readThreadAPIError(response, \"Failed to update conversation.\"),\n    );\n  }\n\n  return (await response.json()) as ThreadMetadataPatchResponse;\n}\n\nexport async function compactThreadContext(\n  threadId: string,\n  options: CompactThreadContextOptions = {},\n): Promise<ThreadCompactResponse> {\n  const response = await fetchWithAuth(\n    `${getBackendBaseURL()}/api/threads/${encodeURIComponent(threadId)}/compact`,\n    {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/frontend/src/core/threads/api.ts#L111-L147","documentation":"Thrown when PATCH /api/threads/{id} with {metadata} returns non-2xx. This is the rename/tag/update-conversation call; readThreadAPIError surfaces the backend reason. 404 means the thread no longer exists, 409 usually a conflicting concurrent patch (metadata merge failure), 422 metadata that fails the backend's schema/size limits.","triggerScenarios":"Renaming a thread deleted in another tab (404); two rapid metadata patches racing (409); metadata values exceeding size limits or containing disallowed keys (422); session expired (401).","commonSituations":"Editing title/tags while an agent run also updates thread metadata; very long pasted titles; multi-tab usage.","solutions":["On 404, drop the local thread from the list — it was deleted server-side","On 409, re-fetch the thread, re-apply the user's edit onto fresh metadata, patch again","Keep metadata values within backend limits (truncate long titles client-side)","Debounce rapid successive edits into one PATCH"],"exampleFix":"// before\nawait patchThreadMetadata(threadId, {title});\n\n// after\ntry {\n  await patchThreadMetadata(threadId, {title});\n} catch (e) {\n  if (/409|conflict/i.test(e.message)) {\n    await reloadThread(threadId);\n    await patchThreadMetadata(threadId, {title}); // one re-apply\n    return;\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"function metadataPatchOk(metadata: ThreadMetadataPatch): boolean {\n  const s = JSON.stringify(metadata);\n  return s.length <= 4096 && Object.values(metadata).every((v) => v !== undefined);\n}","typeGuard":"export function isThreadPatchError(e: unknown): e is Error {\n  return e instanceof Error && e.message.includes('Failed to update conversation.');\n}","tryCatchPattern":"try {\n  return await patchThreadMetadata(threadId, metadata);\n} catch (e) {\n  if (isThreadPatchError(e) && /conflict|409/i.test(e.message)) {\n    await reloadThread(threadId);\n    return patchThreadMetadata(threadId, metadata); // one optimistic retry\n  }\n  throw e;\n}","preventionTips":["Truncate long titles client-side before PATCH","Debounce rapid metadata edits into one request","On 404, remove the thread from local state"],"tags":["threads","metadata","http","frontend"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}