Skip to content

Agent Host: Peer-chat subagent replay corrupts backend session URI #329430

Description

Does this issue occur when all extensions are disabled?: N/A — this is in the core Agent Host peer-chat restore path

Steps to Reproduce:

  1. Open a persisted Copilot CLI Agent Host session that has an additional/peer chat.
  2. Spawn and complete one or more subagents from that peer chat.
  3. Restart/reload the Agent Host and reopen the peer chat so its SDK history is reconstructed.
  4. Observe history enrichment subscribing to a URI shaped like:
    ahp-chat://<peerChatId>/<base64(parentSession)>/subagent/<toolCallId>

Expected:

The restored tool call points to the canonical child chat URI:
ahp-chat://subagent/<base64(parentSession)>/<toolCallId>, and its inner calls are restored.

Actual:

History enrichment repeatedly fails. For example:

Failed to enrich history with subagent calls:
ahp-chat://b50562c0-bc92-4739-b5df-6fd1db072283/Y29waWxvdGNsaTovMWI1MWY3YjEtM2YxNi00MzFlLTkzNTMtYjM2Y2RiNjhiOWQw/subagent/call_JJf7M32l2KevaEOd63visZCG

Session not found on backend:
copilotcli:/1b51f7b1-3f16-431e-9353-b36cdb68b9d0<binary replacement characters>

The backend session exists and the peer chat resumes successfully immediately before these subscription failures. The persisted SDK event log also contains the completed subagent and its child events.

Root cause

Commit dfddfedcf4c9 (agentHost: separate peer chat session and storage URIs, part of #326063) changed CopilotAgentSession history reconstruction to use _storageUri instead of sessionUri:

  • _computeMappedEvents() now calls mapSessionEvents(this._storageUri, ...).
  • getSubagentSessions() now uses this._storageUri as parentSessionStr.

For a peer chat, _storageUri is an ahp-chat channel URI, not a backend session URI. mapSessionEvents() passes that value to buildSubagentSessionUri(), which appends /subagent/<toolCallId> and produces the malformed resource above.

When parseChatUri() later receives that URI, it treats the entire path—including /subagent/<toolCallId>—as base64. / and the suffix letters are valid base64 characters, so decoding does not throw. It decodes the first segment to the valid copilotcli:/... URI and appends deterministic binary garbage from the suffix. AgentService.restoreSession() then correctly fails metadata lookup for that corrupted session URI.

This was reproduced byte-for-byte by decoding the malformed path. The AHP snapshot confirms the restored tool has:

  • _meta.toolKind: "subagent"
  • no _meta.subagentChatUri
  • a ToolResultSubagentContent.resource containing the malformed peer-chat-plus-session-suffix URI

There is a second exposure point: CopilotAgent.getSubagentSessions() deliberately returns no children for non-default chats, so peer-chat subagent state is not eagerly registered and no canonical catalog resource overrides the malformed replay fallback.

The same logical-session/storage-routing conflation also makes replay derive peer-chat MCP identities from scheme ahp-chat and the encoded path rather than the actual provider/session ID.

Suggested fix

  • Keep logical backend sessionUri separate from storage/routing URI in replay mapping.
  • Generate and stamp buildSubagentChatUri(parentSessionUri, toolCallId) for restored subagent tool metadata.
  • Restore/register subagent chat state discovered in peer-chat history.
  • As defense in depth, make parseChatUri() reject unexpected extra path segments for non-subagent authorities rather than decoding them as base64.

Changing only parseChatUri() would avoid the binary corruption but would not restore peer-chat subagent history; the malformed resource must be fixed at its producer.

Metadata

Metadata

Labels

agent-hostIssues for the agent-host implementation (not the protocol)bugIssue identified by VS Code Team member as probable bug

Type

No type

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions