[BUG] Peer agent messages render as raw <agent-message> markup in the live queue
Preflight Checklist
- [x] I searched existing issues and did not find this exact live-queue peer rendering bug.
- [x] This is a single bug report.
- [x] Reproduced on the latest Claude Code version.
What's Wrong?
When a background agent sends a message to the main session while the main session is busy, the live queue renders the peer message as a normal user prompt. The TUI shows the prompt marker followed by the raw internal wrapper:
❯ <agent-message from="peer-name">
message body
</agent-message>
After the queued command is consumed, the same message is rendered correctly in transcript history as a peer message (Message from ...). This makes the issue transient and easy to miss.
The queued item itself still has the correct peer metadata:
{
"type": "queued_command",
"prompt": "<agent-message from=\"peer-name\">\nmessage body\n</agent-message>",
"origin": {
"kind": "peer",
"from": "peer-name",
"senderTaskId": "...",
"body": "message body"
},
"isMeta": true
}
This is not malformed API output. The wrapper is created and queued locally by Claude Code.
What Should Happen?
Peer messages in the live queue should use the same peer-specific presentation as consumed transcript entries. The internal agent-message wrapper should never be displayed as a user prompt.
Steps to Reproduce
- Start Claude Code 2.1.232.
- Launch one or more background agents.
- Have each agent call
SendMessageto send a short message to the main session. - Immediately keep the main session busy with a command such as
sleep 20. - Observe incoming peer messages while the command is still running.
- The live queue displays the prompt marker and raw
<agent-message>wrapper. - Let the command finish and the queue be consumed.
- The transcript then displays the same entry correctly as
Message from ....
The behavior was also observed in a real session on 2.1.221 and remains reproducible on 2.1.232.
Implementation Evidence
In the bundled 2.1.232 implementation, the live queue path converts queued commands with a function equivalent to:
function IRw(command) {
let content = command.value;
if (command.mode === "bash" && typeof content === "string") {
content = `<bash-input>${content}</bash-input>`;
}
return An({ content });
}
The live queue component (vzh) maps queued commands through this conversion and passes the generated messages to the generic static message renderer. The conversion only preserves command.value; it drops command.origin, senderTaskId, and isMeta.
The consumed transcript path is different. Its queued-command branch checks whether the origin is a peer and then uses the dedicated peer renderer, which strips the wrapper and displays Message from ....
A likely fix is to preserve peer origin metadata when constructing live queue messages, or render live queued commands through the existing queued-command peer branch instead of converting them to generic messages.
Environment
- Claude Code: 2.1.232
- Also observed: 2.1.221
- OS: macOS
- Shell: zsh
- Model: multiple models; rendering appears model/API independent
- API platform: Other/custom endpoint, but the reproduction and transcript show the affected item is created by the local peer message queue rather than an API response
- Regression status: unknown