{"record":{"id":"227f0c01bbaa6950","repo":"bytedance/deer-flow","slug":"http-response-status-response-statustext-227f0c","errorCode":null,"errorMessage":"HTTP ${response.status}: ${response.statusText}","messagePattern":"HTTP (.+?): (.+?)","errorType":"exception","errorClass":"ArtifactRequestError","httpStatus":null,"severity":"error","filePath":"frontend/src/core/artifacts/api.ts","lineNumber":48,"sourceCode":"  filepath,\n  content,\n  expectedSha256,\n}: {\n  threadId: string;\n  filepath: string;\n  content: string;\n  expectedSha256: string;\n}): Promise<ArtifactUpdateResponse> {\n  const response = await fetch(urlOfArtifact({ filepath, threadId }), {\n    method: \"PUT\",\n    headers: { \"Content-Type\": \"application/json\" },\n    body: JSON.stringify({\n      content,\n      expected_sha256: expectedSha256,\n    }),\n  });\n  if (!response.ok) {\n    throw new ArtifactRequestError(\n      response.status,\n      await readErrorDetail(response),\n    );\n  }\n  return response.json() as Promise<ArtifactUpdateResponse>;\n}\n","sourceCodeStart":30,"sourceCodeEnd":55,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/frontend/src/core/artifacts/api.ts#L30-L55","documentation":"Thrown when PUT /api/artifacts/{filepath} (with ?thread_id=) returns non-2xx while saving artifact content. The request is an optimistic-concurrency write: it sends expected_sha256 so the backend can reject divergent versions. 409 means the artifact changed since it was read; 404 means the artifact path no longer exists.","triggerScenarios":"Two editors saving the same artifact (second PUT gets 409 because expected_sha256 no longer matches); saving after the artifact was deleted or the thread was garbage-collected (404); saving content larger than the backend limit (413).","commonSituations":"Same thread open in two tabs; long editing session while an agent turn rewrites the artifact underneath; clicking save after the backend pruned old thread artifacts.","solutions":["On 409, re-fetch the artifact (loader), re-apply or merge the edit on the fresh sha256, and PUT again","On 404, verify the thread still exists (GET /api/threads/{id}) and that the filepath is unchanged","Compute expectedSha256 from the content actually loaded, not a cached/stale value","Disable the save button while a PUT is in flight to prevent double-submit races"],"exampleFix":"// before\nawait updateArtifact({filepath, threadId, content, expectedSha256});\n\n// after\ntry {\n  await updateArtifact({filepath, threadId, content, expectedSha256});\n} catch (e) {\n  if (e instanceof ArtifactRequestError && e.status === 409) {\n    const fresh = await loadArtifact(url);\n    return offerMergeOrOverwrite(fresh, content); // re-PUT with fresh.sha256\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"function canSubmitArtifact(content: string, expectedSha256: string): boolean {\n  return expectedSha256.length === 64 && /^[a-f0-9]{64}$/.test(expectedSha256) && content.length > 0;\n}","typeGuard":"export function isArtifactRequestError(e: unknown): e is ArtifactRequestError {\n  return e instanceof ArtifactRequestError;\n}","tryCatchPattern":"try {\n  await updateArtifact({filepath, threadId, content, expectedSha256});\n} catch (e) {\n  if (isArtifactRequestError(e) && e.status === 409) {\n    const fresh = await loadArtifact(urlOfArtifact({filepath, threadId}), {});\n    return promptMerge(fresh, content); // re-PUT with fresh sha\n  }\n  throw e;\n}","preventionTips":["Always derive expectedSha256 from the content you last loaded","Disable Save while a save is in flight","Re-load the artifact when the tab regains focus"],"tags":["artifacts","concurrency","http","sha256","frontend"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}