bytedance/deer-flow · warning · HTTPException

Only successful assistant runs can be edited and rerun

Error message

Only successful assistant runs can be edited and rerun

What it means

Raised in _require_successful_source_run (HTTP 409) when the located run record's status is not RunStatus.success. Edit-and-rerun only replays runs that completed cleanly; interrupted, error, or in-flight runs are refused because their checkpointed state may be partial.

Source

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

    return str(status) if status is not None else None


async def _require_successful_source_run(thread_id: str, run_id: str, request: Request) -> RunRecord:
    run_mgr = get_run_manager(request)
    user_id = await get_current_user(request)
    record = await run_mgr.get(run_id, user_id=user_id)
    if record is None:
        # The run-event journal is the authoritative lookup above. This fallback
        # only covers recent in-memory/store hydration gaps for the latest turn.
        records = await run_mgr.list_by_thread(thread_id, user_id=user_id, limit=20)
        record = next((candidate for candidate in records if getattr(candidate, "run_id", None) == run_id), None)
    if record is None:
        raise HTTPException(status_code=409, detail="Could not find source run for assistant message")
    record_thread_id = getattr(record, "thread_id", None)
    if isinstance(record_thread_id, str) and record_thread_id and record_thread_id != thread_id:
        raise HTTPException(status_code=409, detail="Could not find source run for assistant message")
    if _run_status_value(record) != RunStatus.success.value:
        raise HTTPException(status_code=409, detail="Only successful assistant runs can be edited and rerun")
    return record


async def _find_interrupted_target_run_id(
    thread_id: str,
    source_human: Any,
    request: Request,
) -> str | None:
    source_run_id = _message_additional_kwargs(source_human).get("run_id")
    if not isinstance(source_run_id, str) or not source_run_id:
        return None

    run_mgr = get_run_manager(request)
    user_id = await get_current_user(request)
    record = await run_mgr.get(source_run_id, user_id=user_id)
    if record is None:
        records = await run_mgr.list_by_thread(thread_id, user_id=user_id, limit=20)
        record = next(

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Check the run's status via the runs list endpoint and only rerun runs with status 'success'.
  2. For failed runs, use the normal run/retry path (send a new message) rather than edit-rerun.
  3. Wait for in-flight runs to finish before initiating edit.
  4. If a run is stuck in a non-terminal status, cancel/cleanup it first via the runs lifecycle endpoint.

Example fix

// before
await editRerun(threadId, runId, newText);
// after
const run = await getRun(threadId, runId);
if (run.status !== 'success') throw new Error(`Run is ${run.status}; only successful runs can be edited`);
await editRerun(threadId, runId, newText);
Defensive patterns

Strategy: validation

Validate before calling

const run = await getRun(threadId, runId);
if (run.status !== 'success') { disableEditAffordance(); return; }

Type guard

function isSuccessfulRun(run: {status: string}): boolean {
  return run.status === 'success';
}

Try / catch

try { await editRerun(threadId, runId, text); } catch (e) { if (e.status === 409 && /successful assistant runs/.test(e.detail)) { notify('This turn did not complete successfully; send a new message instead'); } else throw e; }

Prevention

When it happens

Trigger: Rerunning a run whose status is 'error' (model failure), 'interrupted' (human-in-the-loop pause or cancel), or still running.

Common situations: User retries a failed run via the edit endpoint instead of a normal retry; editing a turn whose previous run was cancelled; racing a still-streaming run.

Related errors


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