unslothai/unsloth · error · ResearchConflictError
Assistant message does not match this research run
Error message
Assistant message does not match this research run
What it means
ResearchConflictError raised while binding a research run to its assistant chat message: the UPDATE precondition failed — the target message exists but does not match the run. The guard requires the message's thread_id to equal the run's thread, role == 'assistant', parent_id == the run's user message, no prior run id (or the same one), and (when unclaimed) no existing answer/source parts. Any mismatch aborts so a run can never hijack or overwrite another run's message.
Source
Thrown at studio/backend/storage/research_runs_db.py:214
# reply carries text/source parts that _update_assistant drops on completion,
# so binding one silently overwrites an existing answer.
existing_answer = any(
isinstance(part, dict)
and (
(part.get("type") == "text" and (part.get("text") or "").strip())
or part.get("type") == "source"
)
and part.get("researchRunId") is None
for part in _loads(message["content_json"], [])
)
if (
message["thread_id"] != thread_id
or message["role"] != "assistant"
or message["parent_id"] != user_message_id
or existing_run_id not in (None, run_id)
or (existing_run_id is None and existing_answer)
):
raise ResearchConflictError(
"Assistant message does not match this research run"
)
merged_metadata = (
dict(existing_metadata) if isinstance(existing_metadata, dict) else {}
)
merged_metadata.update(metadata)
conn.execute(
"UPDATE chat_messages SET metadata_json=? WHERE id=?",
(json.dumps(merged_metadata, ensure_ascii = False), assistant_message_id),
)
conn.execute(
"""
INSERT INTO research_runs
(id, owner_subject, thread_id, user_message_id, assistant_message_id,
status, config_json, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 'planning', ?, ?, ?)
""",
(View on GitHub (pinned to 203007d190)
Solutions
- Always use the assistant message id returned by the create-run call for that exact run — never a cached one.
- Ensure the id points to the just-created empty assistant reply (role assistant, parent = the triggering user message).
- On conflict, re-fetch the thread's messages and restart the research run cleanly rather than rebinding.
Example fix
# before: rebinding an old message to a new run run = create_research_run(thread_id, user_msg_id, assistant_message_id=old_assistant_id) # after: create a fresh assistant message per run new_msg = create_message(thread_id, role='assistant', parent_id=user_msg_id) run = create_research_run(thread_id, user_msg_id, assistant_message_id=new_msg.id)
Defensive patterns
Strategy: validation
Validate before calling
# Verify the message is bindable BEFORE creating the run msg = get_message(assistant_message_id) assert msg is not None, 'message missing' assert msg['thread_id'] == thread_id, 'wrong thread' assert msg['role'] == 'assistant', 'not an assistant message' assert msg['parent_id'] == user_message_id, 'wrong parent' assert existing_run_id_of(msg) in (None, run_id), 'already bound' assert not has_answer_parts(msg), 'message already carries an answer' create_research_run(thread_id, user_message_id, assistant_message_id)
Type guard
def is_bindable_assistant_message(msg, thread_id: str, user_message_id: str) -> bool:
return (
msg is not None
and msg['thread_id'] == thread_id
and msg['role'] == 'assistant'
and msg['parent_id'] == user_message_id
and msg.get('researchRunId') is None
) Try / catch
try:
bind_run_to_message(run_id, assistant_message_id)
except ResearchConflictError:
# message is claimed/incompatible: create a FRESH assistant message and rebind
fresh = create_empty_assistant_message(thread_id, parent_id=user_message_id)
bind_run_to_message(run_id, fresh.id) Prevention
- Create a new empty assistant message per run; never reuse ids across runs
- Take message ids from the create-run response, not local cache, after forks/edits
- On retry paths, re-derive both user and assistant message ids from server state
When it happens
Trigger: Passing an assistant_message_id from a different thread; the id belonging to a user-role message; the message already bound to another researchRunId; the message already carries answer/source parts tagged with another run; parent chain changed because the thread was edited/forked.
Common situations: Client reuses a cached message id after the thread was forked or re-created; retry logic pairing a NEW run with the OLD run's assistant message; concurrent runs in one thread racing to claim the same message.
Related errors
- This thread already has a Deep Research run
- A diffusion load is already in progress.
- deadline reached while pacing before {method} {_redact_url(u
- job already running
- A transformers installation is replacing the latest sidecar;
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/a940970a3e524d75.
Report an issue: GitHub.