bytedance/deer-flow · info · HTTPException

Edited message cannot be empty

Error message

Edited message cannot be empty

What it means

Raised in _prepare_edit_regenerate_payload (HTTP 409) when the replacement text for an edited user message strips to an empty string. An edit that produces no user content cannot be replayed, so it is rejected up front before any checkpoint access.

Source

Thrown at backend/app/gateway/routers/thread_runs.py:721

        # the older automatically generated title (#4457).
        regenerate_input["title"] = latest_title
    return RegeneratePrepareResponse(
        input=regenerate_input,
        checkpoint=checkpoint,
        metadata=metadata,
        target_run_id=target_run_id,
    )


async def _prepare_edit_regenerate_payload(
    thread_id: str,
    human_message_id: str,
    replacement_text: str,
    request: Request,
) -> EditRegeneratePrepareResponse:
    normalized_text = replacement_text.strip()
    if not normalized_text:
        raise HTTPException(status_code=409, detail="Edited message cannot be empty")

    accessor, latest_config = await build_thread_checkpoint_state_accessor(request, thread_id=thread_id)
    try:
        latest_checkpoint = await accessor.aget(latest_config)
    except Exception as exc:
        logger.exception("Failed to read latest checkpoint for edit replay thread %s", thread_id)
        raise HTTPException(status_code=500, detail="Failed to read latest checkpoint") from exc
    latest_checkpoint_id = _checkpoint_configurable(latest_checkpoint).get("checkpoint_id")
    if not latest_checkpoint_id:
        raise HTTPException(status_code=404, detail=f"Thread {thread_id} has no checkpoint")

    messages = _checkpoint_messages(latest_checkpoint)
    if _has_active_goal(latest_checkpoint):
        raise HTTPException(status_code=409, detail="Cannot edit while a goal is active")

    _, source_human, _, source_ai, source_message_ids = _latest_editable_turn(messages, human_message_id)
    source_text = get_original_user_content_text(_message_content(source_human), _message_additional_kwargs(source_human)).strip()
    if normalized_text == source_text:

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Client-side validate the edit box is non-empty (after trim) before enabling submit.
  2. Check the request field name matches the endpoint's schema exactly.
  3. Send the trimmed text explicitly in the request body.

Example fix

// before
await editPrepare(threadId, humanId, draftText);
// after
const text = draftText.trim();
if (!text) return;
await editPrepare(threadId, humanId, text);
Defensive patterns

Strategy: validation

Validate before calling

const text = (draft ?? '').trim();
if (!text) { keepEditorOpen(); return; } // do not call the endpoint

Type guard

function isValidEditText(text: unknown): text is string {
  return typeof text === 'string' && text.trim().length > 0;
}

Try / catch

try { await editPrepare(threadId, humanId, text); } catch (e) { if (e.status === 409 && /cannot be empty/.test(e.detail)) { showFieldError('Message cannot be empty'); } else throw e; }

Prevention

When it happens

Trigger: POST to the edit-prepare endpoint with a body whose replacement/new_message field is '', whitespace-only, or missing.

Common situations: Frontend sends the form before the user types; whitespace-only paste; JSON field name mismatch (e.g. 'text' vs 'new_message') silently defaulting to empty.

Related errors


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