[BUG] Peer agent messages render as raw <agent-message> markup in the live queue

Status Open
Reported on v2.1.232
Maintainer reply None cached
Activity 0 comments · opened Aug 14, 2026

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

  1. Start Claude Code 2.1.232.
  2. Launch one or more background agents.
  3. Have each agent call SendMessage to send a short message to the main session.
  4. Immediately keep the main session busy with a command such as sleep 20.
  5. Observe incoming peer messages while the command is still running.
  6. The live queue displays the prompt marker and raw <agent-message> wrapper.
  7. Let the command finish and the queue be consumed.
  8. 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

View original on GitHub ↗