bytedance/deer-flow · warning · HTTPException

Thread has a run in flight. Set the goal after the run finis

Error message

Thread has a run in flight. Set the goal after the run finishes.

What it means

409 from PUT /threads/{thread_id}/goal when reserve_checkpoint_write raises ConflictError — the thread currently has an in-flight run holding the checkpoint write reservation, and goal writes are excluded to avoid clobbering live state. The error is deliberate serialization, telling the caller to wait.

Source

Thrown at backend/app/gateway/routers/threads.py:1105

@router.put("/{thread_id}/goal", response_model=ThreadGoalResponse)
@require_permission("threads", "write", owner_check=True)
async def set_thread_goal(thread_id: ThreadId, body: ThreadGoalRequest, request: Request) -> ThreadGoalResponse:
    """Set or replace the active goal for a thread.

    ``/chats/new`` pages already hold a generated UUID before the first run, so
    this endpoint creates the missing thread checkpoint on demand.
    """
    checkpointer = get_checkpointer(request)
    try:
        goal = build_goal_state(body.objective, max_continuations=body.max_continuations)
        async with reserve_checkpoint_write(request, thread_id, user_id=get_effective_user_id()):
            await _ensure_thread_for_goal(thread_id, request)
            await write_thread_goal(checkpointer, thread_id, goal, as_node="goal", create_if_missing=True)
    except ValueError as exc:
        raise HTTPException(status_code=422, detail=str(exc)) from exc
    except ConflictError:
        raise HTTPException(status_code=409, detail="Thread has a run in flight. Set the goal after the run finishes.") from None
    except HTTPException:
        raise
    except Exception:
        logger.exception("Failed to set goal for thread %s", sanitize_log_param(thread_id))
        raise HTTPException(status_code=500, detail="Failed to set thread goal") from None
    return ThreadGoalResponse(goal=goal)


@router.delete("/{thread_id}/goal", response_model=ThreadGoalResponse)
@require_permission("threads", "write", owner_check=True)
async def clear_thread_goal(thread_id: ThreadId, request: Request) -> ThreadGoalResponse:
    """Clear the active goal for a thread."""
    checkpointer = get_checkpointer(request)
    try:
        async with reserve_checkpoint_write(request, thread_id, user_id=get_effective_user_id()):
            await write_thread_goal(checkpointer, thread_id, None, as_node="goal")
    except ConflictError:
        raise HTTPException(status_code=409, detail="Thread has a run in flight. Clear the goal after the run finishes.") from None

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Wait for the active run to finish (poll GET /threads/{id} status or the stream completion) and retry the PUT.
  2. In the UI, disable goal editing while the thread status is not idle.
  3. If no run is actually active but the 409 persists, the reservation leaked — restart the Gateway or clear the reservation state per its docs, then retry.
  4. Sequence goal updates between runs in automation rather than racing them.

Example fix

// before
await api.put(`/threads/${id}/goal`, {objective}); // 409 during a run
// after
let s = await api.get(`/threads/${id}`).status;
while (s !== 'idle') { await waitForRunCompletion(id); s = (await api.get(`/threads/${id}`)).status; }
await api.put(`/threads/${id}/goal`, {objective});
Defensive patterns

Strategy: retry

Validate before calling

const {status} = await api.get(`/api/threads/${id}`);
if (status !== 'idle') { /* queue the goal update; PUT after the run completes */ }

Type guard

async function threadIdle(id: string): Promise<boolean> {
  const t = await api.get(`/api/threads/${id}`);
  return t.status === 'idle';
}

Try / catch

try { await api.put(`/api/threads/${id}/goal`, body); }
catch (err) {
  if (err.status === 409 && /run in flight/.test(err.detail)) { await waitForRunCompletion(id); retryOnce(); }
  else throw err;
}

Prevention

When it happens

Trigger: PUT /goal while a run/stream on the same thread is executing (agent streaming a response, background goal continuation active), or a stale reservation left by a run that died without releasing.

Common situations: User edits the goal in the UI while the agent is mid-run; automated loops setting goals concurrently with runs; a crashed worker whose reservation was not reclaimed yet.

Related errors


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