DeepSeek Harness duplicate user bubble: queued echo and log
"Ready?" appears twice in the transcript, both bubbles timestamped 00:17, each with its own copy button — it looks like the message was sent twice. But the session log contains exactly one real user message for that send, with the rest being automatically injected context (#10808). Count the data layer and the display layer separately and the mystery disappears.
DeepSeek Harness: Layer 1: the data layer really holds one
Only one event in the log has source.kind === "user"; the other same-role events are not yours.
- Open the session log
session.v4.jsonl.zstdand locate the time of that send. - You will see four
user/messageentries: thekind=userone is "Ready?". - The other three are
kind=memmy-memory,kind=runtime-context, andkind=time-context. - The latter three also have
roleuser, but they are not user input; cadence injects them automatically. - So the data layer is clean: one input, no double billing, no duplicate answer.
00:17:41 user/message kind=user "Ready?"
00:17:41 user/message kind=memmy-memory "<memmy_l3_world_model ..."
00:17:41 user/message kind=runtime-context "Current runtime context ..."
00:17:41 user/message kind=time-context "Time sampled ..."
Four lines share one timestamp, but only the first is yours. That determines every later judgment: bubble count cannot be used to infer message count.
DSH plugin: Layer 2: why the display turns into two
The queued echo is hidden from the transcript but excluded from the deduplication set, so it renders as its own bubble.
- The frontend keeps a pending ledger,
pendingSubmissions, recording the placement of each optimistically displayed message. - The deduplication set
localIdscomes frompendingSubmissions.filter(s => s.placement !== "queued").map(s => s.requestId). - Note that filter — queued is excluded from the set.
- On the other side,
visibleSubmissionshides queued submissions from the transcript. - But in the
inboxSteeringrender logic, anysource.kind === "user"with anrpcIdlooks itself up inlocalIds. - Not found (because queued is not in the set) → this entry renders directly.
- After the message is persisted, the transcript renders it again → two bubbles.
DeepSeek Harness: Layer 3: why it only happens while queued
Sending while the agent is idle takes the next-turn path, where enqueue and dequeue happen almost simultaneously, so you see no duplicate.
- The event chain the reporter recorded: when sending during a run, the first
agent/inbox/splicedtargetsnext-step. - A second splice follows with
removedCount: 1. - Only then does
user/message kind=userappear. - Another send while the agent was idle took
next-turn, enqueue and dequeue happened together, and only one bubble appeared. - Conclusion: the trigger is "the message enters the queued path", unrelated to plugins — the reporter ruled out third-party plugins one by one.
DSH Plugin: Layer 4: why the data layer is not a concern
It is only the display layer drawing one extra time; session content, model calls, and billing are unaffected.
- The model reads one user message.
- The copy button appears twice, but copies the same content.
- After a refresh or reopening the session, the bubble count should return to one.
- So it is an experience problem, not a data integrity problem.
- The report says so explicitly: "the data is fine, the frontend just rendered the same message twice".
DeepSeek Harness: Version status and fix
Two commits fixed this path, but both are in 0.2.2-alpha.2, not rc.4.
- The first,
ae07093132d: keeps the transcript stable when a turn ends and a steering message lands. - One of its descriptions reads "the queued echo waits for the Inbox projection rather than retiring on the persisted splice, so the queue dock does not blink for a frame".
- The second,
a1659120a38: withdraws the echo when the queued echo's Inbox row is cancelled. - Both commits changed session state files and their pending submission tests.
- Both are in
f3803c96(0.2.2-alpha.2), not in504a01a9(rc.4). - The data layer needs no change, and neither does the log format.
DSH plugin: How to confirm this is the issue
All three signs together and you have it: character-identical content, identical timestamps, and exactly one kind=user in the log.
- Compare the two bubbles' text for exact equality, punctuation included.
- Compare timestamps down to the same minute.
- Count
user/messageentries withkind=userin the log. - Recall the send timing: was the agent running a tool call at the time?
- Send another message while the agent is idle and see whether only one bubble appears.
Cautions
Count log events before assuming a duplicate send.
- Do not investigate the network as "duplicate sends". The log already proves there is one.
- Do not delete the session and start over. A refresh or reopening restores the single-bubble display.
- Do not mistake auto-injected events for user messages. Their
roleis user too. - It still appears on rc.4. The fix is only on the alpha line.
- Attach timestamps and log counts when reporting. That is the fastest evidence separating a display issue from a real duplicate.
If your two bubbles differ slightly in content, they may be two real messages with completely different logic — check the log first.
Conversely, if the log really contains two kind=user entries, it really was sent twice, and you should investigate the input channel rather than rendering.See DeepSeek Harness blank sessions and missing auto titles for the related case.
FAQ
No, it was not sent twice: the DeepSeek Harness session log for that send has exactly one event with source.kind === "user"; the rest are memory, runtime-context, and time-context events injected automatically by cadence. The two bubbles are a pure frontend display issue.
It only happens when sending while the agent is running because that send takes the queued path (placement === "queued") in DeepSeek Harness. The queued echo is hidden from the transcript yet also excluded from the deduplication set, so the queued entry renders as its own bubble, and the persisted message renders a second time.
No, it does not cause double billing or duplicate answers: the DeepSeek Harness data layer holds one user message and the model processes it once. The issue affects display only, not session content or cost.
DeepSeek Harness fixed it in commits ae07093132d and a1659120a38 (both 10-27, after rc.4), and both are in 0.2.2-alpha.2 rather than rc.4. So the double bubble still appears on rc.4.
To confirm the duplicate quickly in DeepSeek Harness, check whether the two bubbles have exactly the same timestamp and character-identical content, then count user/message events with kind=user in the session log; exactly one of each means a display-layer duplicate.
Related Terms
- queued placement
- Queued placement is the position marker used when a message is submitted while the agent is running. It puts the message into a queue first and merges it into the conversation when the current turn ends or the next step begins.— https://github.com/deepseek-ai/deepseek-harness/discussions/10808
- pendingSubmissions
- pendingSubmissions is the frontend ledger of pending messages, recording each optimistically displayed submission and its placement. Deduplication builds its set from the requestIds here.— https://github.com/deepseek-ai/deepseek-harness/discussions/10808
- localIds
- localIds is the deduplication set used during rendering, built from pending submission requestIds. The current implementation excludes queued submissions, so the queued entry is never deduplicated.— https://github.com/deepseek-ai/deepseek-harness/discussions/10808
- agent/inbox/spliced
- agent/inbox/spliced is the session event indicating an input was spliced into the current step or the next turn. It carries the target position and removal count, which reveals whether the message took the queue or the next-turn path.— https://github.com/deepseek-ai/deepseek-harness/discussions/10808
Sources
- #10808 — after sending, the same user message shows two bubbles (the data layer has only one)· deepseek-ai (GitHub Discussions)
- deepseek-ai/deepseek-harness (source repository)· GitHub