Skip to content

TUI renders a user message twice when a prompt reuses an existing messageID #50945

Closed as not planned

Description

@AlexDochioiu

Version

opencode 1.18.32 (opencode serve + opencode attach)

Summary

Sending a prompt with the messageID of an existing message rewrites that message with a new time.created, but keeps the same id. The TUI sync store keys messages by time.created + id (packages/tui/src/context/sync.tsx, messageKey). When the second message.updated arrives, the lookup misses, so the TUI inserts a second row for the same id. Parts are keyed by message id, so both rows render the same content.

Storage is correct: the session has one message with one part. Reloading the session shows it once.

Repro

B=http://localhost:4096; H='x-opencode-directory: /tmp/dir'
SID=$(curl -s -X POST $B/session -H 'content-type: application/json' -H "$H" -d '{}' | jq -r .id)
# reserve an empty message
MID=$(curl -s -X POST $B/session/$SID/message -H 'content-type: application/json' -H "$H" \
  -d '{"parts":[],"noReply":true}' | jq -r .info.id)
# reuse it
curl -s -X POST $B/session/$SID/message -H 'content-type: application/json' -H "$H" \
  -d "{\"messageID\":\"$MID\",\"parts\":[{\"type\":\"text\",\"text\":\"hello\"}],\"noReply\":true}"

With opencode attach $B -s $SID open, "hello" appears in two user rows. The event stream shows two message.updated events for $MID with different time.created values.

Suggested fix

Key TUI messages by id alone. Ascending message ids already sort by creation time, so time.created in the key only breaks the lookup when a message is rewritten. Alternatively, keep the original time.created when a prompt reuses an existing messageID.

Activity

  1. myagizmaktav commented on Sep 24, 2026

    @myagizmaktav

    I'd like to take this one.

    Confirmed on dev: packages/tui/src/context/sync.tsx binary-searches the per-session message list by messageKey = time.created + id. When a prompt reuses an existing messageID, the second message.updated has a new time.created, the lookup misses, and the message gets spliced in again as a second row.

    Plan: in the message.updated handler, look up an existing entry by id first. If one is found, replace it and re-sort it into its creation-time position, so the ordering from #40994 stays intact. The creation-time binary search stays for genuinely new messages. The regression test goes in test/cli/cmd/tui/sync-live-hydration.test.tsx: emit message.updated twice for the same id with different time.created and assert that the session holds exactly one entry with the latest info.

  2. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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