bytedance/deer-flow · error · ArtifactRequestError
HTTP ${response.status}: ${response.statusText}
Error message
HTTP ${response.status}: ${response.statusText} What it means
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.
Source
Thrown at frontend/src/core/artifacts/api.ts:48
filepath,
content,
expectedSha256,
}: {
threadId: string;
filepath: string;
content: string;
expectedSha256: string;
}): Promise<ArtifactUpdateResponse> {
const response = await fetch(urlOfArtifact({ filepath, threadId }), {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content,
expected_sha256: expectedSha256,
}),
});
if (!response.ok) {
throw new ArtifactRequestError(
response.status,
await readErrorDetail(response),
);
}
return response.json() as Promise<ArtifactUpdateResponse>;
}
View on GitHub (pinned to 1dd6ba1acb)
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
Example fix
// before
await updateArtifact({filepath, threadId, content, expectedSha256});
// after
try {
await updateArtifact({filepath, threadId, content, expectedSha256});
} catch (e) {
if (e instanceof ArtifactRequestError && e.status === 409) {
const fresh = await loadArtifact(url);
return offerMergeOrOverwrite(fresh, content); // re-PUT with fresh.sha256
}
throw e;
} Defensive patterns
Strategy: retry
Validate before calling
function canSubmitArtifact(content: string, expectedSha256: string): boolean {
return expectedSha256.length === 64 && /^[a-f0-9]{64}$/.test(expectedSha256) && content.length > 0;
} Type guard
export function isArtifactRequestError(e: unknown): e is ArtifactRequestError {
return e instanceof ArtifactRequestError;
} Try / catch
try {
await updateArtifact({filepath, threadId, content, expectedSha256});
} catch (e) {
if (isArtifactRequestError(e) && e.status === 409) {
const fresh = await loadArtifact(urlOfArtifact({filepath, threadId}), {});
return promptMerge(fresh, content); // re-PUT with fresh sha
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Failed to load artifact: ${response.status}
- Failed to load MCP configuration
- Failed to update MCP configuration
- Failed to update MCP server
- setup-status failed: ${response.status}
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/227f0c01bbaa6950.
Report an issue: GitHub.