Skip to content

Potential race condition for displaying streamed messages in the TUI as one single message #196

Description

@GitMurf

I have tried troubleshooting to fix myself but at this point it is best to confirm whether you (others) are able to reproduce similarly this issue I am having. Basically, at different times, especially with the very first message in the conversation, the very end of a message response stream (typically a punctuation like . or ?) gets pushed over into its own new message in the TUI. See the following screenshot:

Image

I have narrowed it down to if I comment out this line of code text = undefined then it works as expected (see below).

Line to comment out:

Image

Resulting updated CORRECT response in one single message:

Image

The problem though is that I have noticed other side effects by commenting out this line of code so I do not think it is the solution. It seems like this is all part of some sort of race condition.

Let me know if you have any questions? Thanks!

Activity

  1. GitMurf commented on Jun 18, 2025

    @GitMurf
    ContributorAuthor

    Also, while I see it most often in the very start of a conversation in the first message, I see it other places as well in the middle of a conversation. So it is NOT only the first message. Just the easiest to repro with the first message.

  2. GitMurf commented on Jun 18, 2025

    @GitMurf
    ContributorAuthor

    Also, while I see it most often in the very start of a conversation in the first message, I see it other places as well in the middle of a conversation. So it is NOT only the first message. Just the easiest to repro with the first message.

    Here is example showing happening in other messages and not just the first one.

    Image

  3. self-assigned this
    on Jun 19, 2025
  4. GitMurf commented on Jun 21, 2025

    @GitMurf
    ContributorAuthor

    Curious if anyone else can confirm similar behavior?

    It seems to have gotten a bit more common the last couple days where most messages have the last token streamed in flow over to a new message.

  5. GitMurf commented on Jun 22, 2025

    @GitMurf
    ContributorAuthor

    @adamdotdevin since I see you lurking this morning ;) could you confirm whether this is an issue you have seen or heard others mention? I am fine if it will take a while to resolve / fix, I just want to confirm I am not crazy because it's driving me nuts that I'm unsure if others can reproduce this or not! 🤣 thanks!!

  6. adamdotdevin commented on Jun 22, 2025

    @adamdotdevin
    Member

    i've actually not seen it or heard anyone else report!

  7. GitMurf commented on Jun 22, 2025

    @GitMurf
    ContributorAuthor

    i've actually not seen it or heard anyone else report!

    I wonder if it is somehow either a WSL thing, windows terminal thing (doubtful) or a GitHub copilot provider thing 🤔 either way I would expect someone else to have hit this... or maybe they aren't as OCD as me and don't care 🤣

  8. GitMurf commented on Jun 22, 2025

    @GitMurf
    ContributorAuthor

    I wonder if it is somehow either a WSL thing, windows terminal thing (doubtful) or a GitHub copilot provider thing 🤔 ...

    Ok @adamdotdevin it seems that it is using the openai compatible github copilot provider! Do you have a way of testing github copilot and/or another openai compatible provider to see if you can repro?

    Here is when I use GitHub Copilot openai compatible provider:

    Image

    Here is when I use regular OpenAI provider with an OpenAI key:

    Image

  9. GitMurf commented on Jun 22, 2025

    @GitMurf
    ContributorAuthor

    Pinging a couple others who I know are experimenting with GitHub Copilot as a provider. Are any of you able to reproduce what I show above? Thanks!! cc @DaviOliveira-dev @thijsvdanker @timotah @palmamartin

  10. timotah commented on Jun 22, 2025

    @timotah

    @adamdotdevin @GitMurf I have had this happen quite consistently using GitHub copilot as a provider. Mostly using the copilot OpenAi models. Most of the time I prompt on the beginning of the session to avoid this and it sometimes works. But this always does happen to me in a new session, same way shown by @GitMurf. This never happens when I use my other provider (only one I use besides copilot is google provider)

  11. GitMurf commented on Jun 22, 2025

    @GitMurf
    ContributorAuthor

    I have had this happen quite consistently using GitHub copilot as a provider. Mostly using the copilot OpenAi models.

    I suspect it has something to do with how the streaming comes back. I used a proxy on my end and below is logging out exactly the data chunks streamed back which maybe can help @adamdotdevin / @thdxr identify the issue / differences from other providers as I tried for a few hours the other day and could not figure it out.

    STREAMED RESPONSES from copilot provider (I included timestamps in front of each chunk just in case something looks off with the timing or something... these timestamps are NOT part of the actual streamed chunks):

    NOTE: to be painfully clear, here is my console log so you see what it is spitting out and what is added to the log by me versus actual real streamed content: [${new Date().toISOString()}] Data chunk: ${chunk.toString()}

    [2025-06-22T17:48:33.353Z] Data chunk: data: {"choices":[],"created":0,"id":"","prompt_filter_results":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"prompt_index":0}]}
    
    
    [2025-06-22T17:48:33.353Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"","role":"assistant"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.354Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"Hello"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.354Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"!"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.355Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":" How"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.355Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":" can"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.355Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":" I"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.356Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":" help"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.356Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":" you"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.356Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":" today"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.357Z] Data chunk: data: {"choices":[{"index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"?"}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.357Z] Data chunk: data: {"choices":[{"finish_reason":"stop","index":0,"content_filter_offsets":{"check_offset":13001,"start_offset":13001,"end_offset":13033},"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":null}}],"created":1750614512,"id":"chatcmpl-BlJ5sEStTs7ALYeQ0bMZezRlFKrrt","usage":{"completion_tokens":11,"completion_tokens_details":{"accepted_prediction_tokens":0,"rejected_prediction_tokens":0},"prompt_tokens":9846,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":9857},"model":"gpt-4.1-2025-04-14","system_fingerprint":"fp_XXXXXXXXXXX"}
    
    
    [2025-06-22T17:48:33.357Z] Data chunk: data: [DONE]
    
  12. mohak34 commented on Jun 23, 2025

    @mohak34
    Contributor

    I have also had this using the github-copilot provider for sonnet 4. It puts out messages after like ?

    I guess I had this while using the Google api also. Could check it and let you know about this

  13. davinpkn commented on Jun 23, 2025

    @davinpkn

    I tried reproducing the issue using GitHub Copilot as the provider, and it happened only once. When testing with Google as the provider, I couldn’t reproduce the issue.
    I'm also on WSL2, i can test on a bare metal linux if needed.

  14. GitMurf commented on Jun 23, 2025

    @GitMurf
    ContributorAuthor

    I'm also on WSL2

    @DaviOliveira-dev I am using windows terminal + arch Linux (wsl2) + zsh shell... curious what you are using just to get a full landscape of the issue? Thanks!

  15. 0ximjosh commented on Jun 24, 2025

    @0ximjosh
    Contributor

    I have also had this issue, using ghostty / zsh extremely consistently using my own model backend. I have a decent idea of what could be causing it. will investigate shortly

  16. 1 remaining item

  17. 0ximjosh commented on Jun 24, 2025

    @0ximjosh
    Contributor

    I am also using ai-sdk/openai-compatible, but i also saw this with "@openrouter/ai-sdk-provider".
    I can confirm this is a race condition. onStepFinish and onFinish are triggering before all the text-delta chunks have been read by result.fullStream in the file you mentioned in the issue desc. Attached are the end of the logs from a session i got this to trigger

    INFO  2025-06-24T04:10:49 +0ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=text-delta part
    INFO  2025-06-24T04:10:49 +0ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +1ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=text-delta part
    INFO  2025-06-24T04:10:49 +0ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +175ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=text-delta part
    INFO  2025-06-24T04:10:49 +1ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +1ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +0ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=text-delta part
    INFO  2025-06-24T04:10:49 +0ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +1ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=text-delta part
    INFO  2025-06-24T04:10:49 +2ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +72ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=text-delta part
    INFO  2025-06-24T04:10:49 +1ms service=session finishReason=stop step finish
    INFO  2025-06-24T04:10:49 +1ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +0ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=text-delta part
    INFO  2025-06-24T04:10:49 +0ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +1ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.part.updated publishing
    INFO  2025-06-24T04:10:49 +0ms service=session reason=stop message finish
    INFO  2025-06-24T04:10:49 +0ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +0ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=text-delta part
    INFO  2025-06-24T04:10:49 +0ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +1ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +0ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=step-finish part
    INFO  2025-06-24T04:10:49 +0ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=step-finish unhandled
    INFO  2025-06-24T04:10:49 +0ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=finish part
    INFO  2025-06-24T04:10:49 +0ms service=session session=ses_85fde6652ffexTSbuiHhMlHMjV type=finish unhandled
    INFO  2025-06-24T04:10:49 +0ms service=bus type=storage.write publishing
    INFO  2025-06-24T04:10:49 +0ms service=bus type=message.updated publishing
    INFO  2025-06-24T04:10:49 +0ms service=session sessionID=ses_85fde6652ffexTSbuiHhMlHMjV unlocking
    INFO  2025-06-24T04:10:49 +0ms service=server duration=3872 response
    INFO  2025-06-24T04:11:39 +49167ms service=bus type=* unsubscribing
    INFO  2025-06-24T04:11:39 +0ms service=server event disconnected
    INFO  2025-06-24T04:11:39 +6ms service=app name=session shutdown
    INFO  2025-06-24T04:11:39 +1ms service=app name=mcp shutdown
    

    Note how we see stop message finish and stop step finish print before the last text-delta.
    We can see this exaggerated in the session json file

    {
      "id": "msg_7a02199b4002xF5X9AoDekeOAW",
      "role": "assistant",
      "parts": [
        {
          "type": "step-start"
        },
        {
          "type": "text",
          "text": "<think>\nOkay, the user wrote \"hello world\". Let me see how to respond. Since the user didn't ask a specific question, I should check if they need help with something. The guidelines say to be concise and direct. Also, mention the help command and feedback link.\n\nI'll keep it short. Maybe just ask how I can assist them today. Include the help options as instructed. Avoid any extra text. Make sure not to use emojis unless asked. Stick to the required format.\n</think>\n\nHow can I help you today? Use `/help` for assistance"
        },
        {
          "type": "text",
          "text": "."
        }
      ],
      "metadata": {
        "assistant": {
          "system": [
            "Redacted",
            "redacted"
          ],
          "path": {
            "cwd": "/Users/josh/github/opencode",
            "root": "/Users/josh/github/opencode"
          },
          "cost": null,
          "tokens": {
            "input": null,
            "output": null,
            "reasoning": 0,
            "cache": {
              "write": 0,
              "read": 0
            }
          },
          "modelID": "deepseek-ai/DeepSeek-R1",
          "providerID": "deepseek"
        },
        "time": {
          "created": 1750738246068,
          "completed": 1750738249939
        },
        "sessionID": "ses_85fde6652ffexTSbuiHhMlHMjV",
        "tool": {}
      }
    }

    Here, note the two "type": "text" in parts. This is specifically from this chunk of code

              case "text-delta":
                if (!text) {
                  text = {
                    type: "text",
                    text: value.textDelta,
                  }
                  next.parts.push(text)
                  break
                } else text.text += value.textDelta
                break

    where we can see why commenting out text = undefined semi-fixes the issue (albeit with side affects) since that would not reset text when onStepFinish triggers prematurely. I havent tried it yet, but @thdxr is there a reason we arent doing the parsing in onChunk? from the docs it seems the async function to onChunk will block processing untill the promise of the current chunk is resolved. If we want to go down that path i can whip that up.

  18. 0ximjosh commented on Jun 24, 2025

    @0ximjosh
    Contributor

    I now also have a way to reproduce this, just middle-man the stream and make the usage token send at the same time as the chunks before it. Im even able to get it to improperly split up to 2-4 tokens if i send enough at the same time.

    Image

    Im using my companies api layer and infra to test this so unfortunately i don't have any code i can share for others to reproduce easily, but im more than happy to work on / test a fix for this. If i have time later this week and this hasnt been fixed i can spin up a server with my middleman people can use to test / repro this bug.

  19. GitMurf commented on Jun 24, 2025

    @GitMurf
    ContributorAuthor

    Awesome work @0ximjosh !!

    Especially thanks for my sanity that I wasn't crazy 🤣

    P.S. LLMs suck at trying to troubleshoot/debug race conditions! Haha

  20. mohak34 commented on Jun 24, 2025

    @mohak34
    Contributor

    Image

    This is using the github provider for sonnet 4. I could not get it with using google in github copilot or using the normal google api. Does it have something to do with only specific models ?

  21. mohak34 commented on Jun 24, 2025

    @mohak34
    Contributor

    Image

    any model using the github copilot provider has this issue.

  22. adamdotdevin commented on Jun 25, 2025

    @adamdotdevin
    Member

    @thdxr this one needs your attention when you have some time

  23. togmund commented on Jun 30, 2025

    @togmund

    This happens to me too, which is breaking a specific workflow for me. If the contents of the final message are to be parsed by some other tool, and that message snips the final character off and sends it as it's own message, I'm hooped.

    Image Image
  24. pwuersch commented on Jul 2, 2025

    @pwuersch

    This also happens for me when using the @ai-sdk/openai-compatible NPM package for the provider and a self-hosted model. I use the following configuration:

    My Configuration and example conversation
    {
      "$schema": "https://opencode.ai/config.json",
      "provider": {
        "self-hosted": {
          "npm": "@ai-sdk/openai-compatible",
          "name": "LLM API",
          "options": {
            "baseURL": "https://<host>/llm/v1"
          },
          "models": {
            "qwen3-30b-a3b": {
              "name": "Qwen3-32b-A3B",
              "id": "qwen/qwen3-32b"
            }
          }
        }
      }
    }
    Image

    Any efforts into fixing this would be greatly appreciated. Until now, this didn't impact any code changes AFAICT. Would still be nice if it were one consistent output.

  25. rekram1-node commented on Dec 28, 2025

    @rekram1-node
    Collaborator

    [automated] Closing due to 90+ days of inactivity. Feel free to reopen if you still need this!

  26. added a commit that references this issue on May 12, 2026
    4e05451
  27. added a commit that references this issue on May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions