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

  1. On 409, re-fetch the artifact (loader), re-apply or merge the edit on the fresh sha256, and PUT again
  2. On 404, verify the thread still exists (GET /api/threads/{id}) and that the filepath is unchanged
  3. Compute expectedSha256 from the content actually loaded, not a cached/stale value
  4. 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

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


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/227f0c01bbaa6950. Report an issue: GitHub.