{"record":{"id":"74ace1ad67cdd341","repo":"bytedance/deer-flow","slug":"thread-has-a-run-in-flight-set-the-goal-after-the","errorCode":null,"errorMessage":"Thread has a run in flight. Set the goal after the run finishes.","messagePattern":"Thread has a run in flight\\. Set the goal after the run finishes\\.","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"backend/app/gateway/routers/threads.py","lineNumber":1105,"sourceCode":"\n@router.put(\"/{thread_id}/goal\", response_model=ThreadGoalResponse)\n@require_permission(\"threads\", \"write\", owner_check=True)\nasync def set_thread_goal(thread_id: ThreadId, body: ThreadGoalRequest, request: Request) -> ThreadGoalResponse:\n    \"\"\"Set or replace the active goal for a thread.\n\n    ``/chats/new`` pages already hold a generated UUID before the first run, so\n    this endpoint creates the missing thread checkpoint on demand.\n    \"\"\"\n    checkpointer = get_checkpointer(request)\n    try:\n        goal = build_goal_state(body.objective, max_continuations=body.max_continuations)\n        async with reserve_checkpoint_write(request, thread_id, user_id=get_effective_user_id()):\n            await _ensure_thread_for_goal(thread_id, request)\n            await write_thread_goal(checkpointer, thread_id, goal, as_node=\"goal\", create_if_missing=True)\n    except ValueError as exc:\n        raise HTTPException(status_code=422, detail=str(exc)) from exc\n    except ConflictError:\n        raise HTTPException(status_code=409, detail=\"Thread has a run in flight. Set the goal after the run finishes.\") from None\n    except HTTPException:\n        raise\n    except Exception:\n        logger.exception(\"Failed to set goal for thread %s\", sanitize_log_param(thread_id))\n        raise HTTPException(status_code=500, detail=\"Failed to set thread goal\") from None\n    return ThreadGoalResponse(goal=goal)\n\n\n@router.delete(\"/{thread_id}/goal\", response_model=ThreadGoalResponse)\n@require_permission(\"threads\", \"write\", owner_check=True)\nasync def clear_thread_goal(thread_id: ThreadId, request: Request) -> ThreadGoalResponse:\n    \"\"\"Clear the active goal for a thread.\"\"\"\n    checkpointer = get_checkpointer(request)\n    try:\n        async with reserve_checkpoint_write(request, thread_id, user_id=get_effective_user_id()):\n            await write_thread_goal(checkpointer, thread_id, None, as_node=\"goal\")\n    except ConflictError:\n        raise HTTPException(status_code=409, detail=\"Thread has a run in flight. Clear the goal after the run finishes.\") from None","sourceCodeStart":1087,"sourceCodeEnd":1123,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/app/gateway/routers/threads.py#L1087-L1123","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wait for the active run to finish (poll GET /threads/{id} status or the stream completion) and retry the PUT.","In the UI, disable goal editing while the thread status is not idle.","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.","Sequence goal updates between runs in automation rather than racing them."],"exampleFix":"// before\nawait api.put(`/threads/${id}/goal`, {objective}); // 409 during a run\n// after\nlet s = await api.get(`/threads/${id}`).status;\nwhile (s !== 'idle') { await waitForRunCompletion(id); s = (await api.get(`/threads/${id}`)).status; }\nawait api.put(`/threads/${id}/goal`, {objective});","handlingStrategy":"retry","validationCode":"const {status} = await api.get(`/api/threads/${id}`);\nif (status !== 'idle') { /* queue the goal update; PUT after the run completes */ }","typeGuard":"async function threadIdle(id: string): Promise<boolean> {\n  const t = await api.get(`/api/threads/${id}`);\n  return t.status === 'idle';\n}","tryCatchPattern":"try { await api.put(`/api/threads/${id}/goal`, body); }\ncatch (err) {\n  if (err.status === 409 && /run in flight/.test(err.detail)) { await waitForRunCompletion(id); retryOnce(); }\n  else throw err;\n}","preventionTips":["Disable goal editing in the UI while the thread is streaming/running.","Sequence goal updates between runs in automation instead of racing them.","A persistent 409 with no visible run indicates a leaked reservation — restart the Gateway rather than retrying harder."],"tags":["threads","goals","concurrency","http-409","conflict"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}