Skip to main content
HTTP status: 409

Error message

Example: conversation already has an active run 82eb9cb7-619e-46bb-ac1c-8a32b0112c1c (waiting on job 9f3c1a20-1f1e-4e6e-b0f4-2e0b6a71cc55): poll GET /agents/runs/82eb9cb7-619e-46bb-ac1c-8a32b0112c1c until it reaches a terminal state, then retry. The parenthesised part is the active run’s most recent live turn — for a run parked on a job or a sleep, this is what it is waiting for. It is omitted when the run hasn’t produced one yet.

When this error occurs

POST /agents/conversations/{id}/runs was called while a run on that conversation was still pending or running. A conversation runs one turn at a time: its message history is appended under an optimistic-concurrency check, so a second concurrent run would burn a workflow and its inference calls only to lose that check at finalize with Concurrent Conversation Append. The API refuses it up front instead. The three terminal states never trigger this — including requires_action, so the run that resumes a paused one with tool_outputs is accepted as normal. An idempotent retry is never refused either: re-posting a client_op_id that already has a run on this conversation returns that run’s row, even while a different run is active.

How to fix

  1. Read the active run id out of detail and poll GET /agents/runs/{active_run_id} until its status is completed, requires_action, or failed.
  2. Re-read the conversation’s version via GET /agents/conversations/{id} — the finished run advanced it.
  3. Post your run with the fresh expected_version.
A run that is waiting on a Narrative job — or parked on sleep or wait_for — can stay non-terminal for a while. live.messages on the run response shows what it is waiting for, so you can tell a working run from a stuck one.

Best-effort guard

The pre-flight check is best-effort. Under a tight race the pre-flight can miss a run that is just starting; the finalize-time CAS (Concurrent Conversation Append) stays the authority for simultaneous posts.