Skip to content

Negative initialStartIndex can resume in the middle of a UI chunk sequence and break AI SDK chat state #1835

@joetifa2003

Description

@joetifa2003

Summary

When using resumable streams with WorkflowChatTransport and a negative initialStartIndex (for example -50), the reconnect response can start in the middle of a logical UI-message part sequence.
That causes the AI SDK client stream processor to throw errors like:
Received reasoning-delta for missing reasoning part with ID "reasoning-0". Ensure a "reasoning-start" chunk is sent before any "reasoning-delta" chunks.
This happened even though the reconnect endpoint correctly returned x-workflow-stream-tail-index as described in the docs.

Versions

  • workflow: 4.2.4
  • @workflow/ai: 4.1.2
  • ai: 6.0.168
  • @ai-sdk/react: 3.0.170

What I expected

I expected negative resume indices to be safe to use for resumable chat streams, as long as:

  • the client uses initialStartIndex: -50
  • the reconnect route returns x-workflow-stream-tail-index
  • the route returns getRun(runId).getReadable({ startIndex })

What actually happened

The reconnect stream started with deltas for an already-open logical part, instead of the matching start chunk.
Example from Chrome SSE output:

data: {"type":"reasoning-delta","id":"reasoning-0","delta":" space in the header"}
data: {"type":"reasoning-delta","id":"reasoning-0","delta":".\n\nNow I"}
...
data: {"type":"reasoning-end","id":"reasoning-0"}
data: {"type":"tool-input-start","toolCallId":"call_exjjt7cc","toolName":"grep"}
...
data: {"type":"finish-step"}
data: {"type":"tool-output-available", ...}
data: {"type":"start-step"}
data: {"type":"reasoning-start","id":"reasoning-0"}

The AI SDK client then throws because it receives reasoning-delta before reasoning-start.

Why this seems surprising

The current docs around negative resume indices talk about transport/chunk offsets and x-workflow-stream-tail-index, but they do not mention that:

  • a negative chunk index can land in the middle of a text-* sequence
  • or in the middle of a reasoning-* sequence
  • or in the middle of a tool-input-* sequence
    The AI SDK UI stream processor enforces those semantic boundaries, so raw chunk-index resume is not always safe for UI chunk replay.

Minimal setup

Client:

new WorkflowChatTransport({
  api: "/api/chat",
  initialStartIndex: -50,
})

Reconnect route:

const run = getRun(params.id)
const stream = run.getReadable({
  startIndex: startIndex ? parseInt(startIndex, 10) : undefined,
})
const tailIndex = await stream.getTailIndex()
return createUIMessageStreamResponse({
  stream,
  headers: {
    "x-workflow-run-id": params.id,
    "x-workflow-stream-tail-index": String(tailIndex),
  },
})

Workaround I had to implement
On the server, when startIndex < 0, I had to:

  • resolve the negative index against the tail index
  • scan backward a bit
  • rewind to the previous safe boundary such as start or start-step
  • only then return the stream
    That fixed the issue.

Request

Can the docs and/or library behavior be clarified here?

Possible directions:

  • document that negative resume indices can cut into the middle of UI part sequences
  • recommend rewinding to a safe boundary before returning resumed streams
  • or make the transport/runtime handle this automatically for UI chunk streams

This was reproduced with DurableAgent streaming UI chunks directly to the chat client.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions