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

Closed
opened 2026-02-16 17:25:21 -05:00 by yindo · 24 comments
Owner

Originally created by @GitMurf on GitHub (Jun 18, 2025).

Originally assigned to: @thdxr on GitHub.

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!

Originally created by @GitMurf on GitHub (Jun 18, 2025). Originally assigned to: @thdxr on GitHub. 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](https://github.com/user-attachments/assets/430b311d-780f-475e-87e9-b029cf25c465) 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](https://github.com/user-attachments/assets/5176c02d-985d-42d7-a098-d05c3cc0d57d) Resulting updated CORRECT response in one single message: ![Image](https://github.com/user-attachments/assets/769900f9-0591-4640-a839-b6fdcdacee47) 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!
yindo closed this issue 2026-02-16 17:25:21 -05:00
Author
Owner

@GitMurf commented on GitHub (Jun 18, 2025):

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.

@GitMurf commented on GitHub (Jun 18, 2025): 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.
Author
Owner

@GitMurf commented on GitHub (Jun 18, 2025):

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

@GitMurf commented on GitHub (Jun 18, 2025): > 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](https://github.com/user-attachments/assets/b93b9030-c283-4a25-a14e-84824b38cacf)
Author
Owner

@GitMurf commented on GitHub (Jun 20, 2025):

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.

@GitMurf commented on GitHub (Jun 20, 2025): 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.
Author
Owner

@GitMurf commented on GitHub (Jun 22, 2025):

@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!!

@GitMurf commented on GitHub (Jun 22, 2025): @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!!
Author
Owner

@adamdotdevin commented on GitHub (Jun 22, 2025):

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

@adamdotdevin commented on GitHub (Jun 22, 2025): i've actually not seen it or heard anyone else report!
Author
Owner

@GitMurf commented on GitHub (Jun 22, 2025):

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 🤣

@GitMurf commented on GitHub (Jun 22, 2025): > 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 🤣
Author
Owner

@GitMurf commented on GitHub (Jun 22, 2025):

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

@GitMurf commented on GitHub (Jun 22, 2025): > 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](https://github.com/user-attachments/assets/866318c1-ba7a-4404-b57d-086280b1422f) ### Here is when I use regular OpenAI provider with an OpenAI key: ![Image](https://github.com/user-attachments/assets/0d926a8c-4e67-4cb7-b6b4-0375acb1c759)
Author
Owner

@GitMurf commented on GitHub (Jun 22, 2025):

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

@GitMurf commented on GitHub (Jun 22, 2025): 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
Author
Owner

@timotah commented on GitHub (Jun 22, 2025):

@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)

@timotah commented on GitHub (Jun 22, 2025): @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)
Author
Owner

@GitMurf commented on GitHub (Jun 22, 2025):

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]
@GitMurf commented on GitHub (Jun 22, 2025): > 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] ```
Author
Owner

@mohak34 commented on GitHub (Jun 23, 2025):

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

@mohak34 commented on GitHub (Jun 23, 2025): 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
Author
Owner

@daviziks commented on GitHub (Jun 23, 2025):

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.

@daviziks commented on GitHub (Jun 23, 2025): 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.
Author
Owner

@GitMurf commented on GitHub (Jun 23, 2025):

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!

@GitMurf commented on GitHub (Jun 23, 2025): > 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!
Author
Owner

@0ximjosh commented on GitHub (Jun 23, 2025):

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

@0ximjosh commented on GitHub (Jun 23, 2025): 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
Author
Owner

@GitMurf commented on GitHub (Jun 23, 2025):

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

I'm wondering if it could be a problem with the ai-sdk/openai-compatible sdk? 🤔 is your own model backend using it as well?

@GitMurf commented on GitHub (Jun 23, 2025): > 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 I'm wondering if it could be a problem with the ai-sdk/openai-compatible sdk? 🤔 is your own model backend using it as well?
Author
Owner

@0ximjosh commented on GitHub (Jun 24, 2025):

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.

@0ximjosh commented on GitHub (Jun 24, 2025): 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 ```json { "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 ```ts 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.
Author
Owner

@0ximjosh commented on GitHub (Jun 24, 2025):

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.

@0ximjosh commented on GitHub (Jun 24, 2025): 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. <img width="1020" alt="Image" src="https://github.com/user-attachments/assets/02dbb0d6-1944-4c11-953e-25ae3577c1fc" /> 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.
Author
Owner

@GitMurf commented on GitHub (Jun 24, 2025):

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

@GitMurf commented on GitHub (Jun 24, 2025): 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
Author
Owner

@mohak34 commented on GitHub (Jun 24, 2025):

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 ?

@mohak34 commented on GitHub (Jun 24, 2025): ![Image](https://github.com/user-attachments/assets/008d28ff-9f15-450d-9b5f-7ed00b2115fd) 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 ?
Author
Owner

@mohak34 commented on GitHub (Jun 24, 2025):

Image

any model using the github copilot provider has this issue.

@mohak34 commented on GitHub (Jun 24, 2025): ![Image](https://github.com/user-attachments/assets/442b8301-1552-4747-9d24-82debc8c6a0f) any model using the github copilot provider has this issue.
Author
Owner

@adamdotdevin commented on GitHub (Jun 25, 2025):

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

@adamdotdevin commented on GitHub (Jun 25, 2025): @thdxr this one needs your attention when you have some time
Author
Owner

@togmund commented on GitHub (Jun 30, 2025):

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
@togmund commented on GitHub (Jun 30, 2025): 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. <img width="490" alt="Image" src="https://github.com/user-attachments/assets/c37420a4-8537-4b54-beb4-6d1d496de7a0" /> <img width="716" alt="Image" src="https://github.com/user-attachments/assets/53af1125-f858-46db-a89a-d67e7c0ca2eb" />
Author
Owner

@pwuersch commented on GitHub (Jul 2, 2025):

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.

@pwuersch commented on GitHub (Jul 2, 2025): 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: <details><summary>My Configuration and example conversation</summary> ```json { "$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" } } } } } ``` <img width="890" alt="Image" src="https://github.com/user-attachments/assets/cb005979-923c-4be7-9d3f-5af163ab9127" /> </details> 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.
Author
Owner

@rekram1-node commented on GitHub (Dec 27, 2025):

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

@rekram1-node commented on GitHub (Dec 27, 2025): [automated] Closing due to 90+ days of inactivity. Feel free to reopen if you still need this!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#135