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:
- Open a persisted Copilot CLI Agent Host session that has an additional/peer chat.
- Spawn and complete one or more subagents from that peer chat.
- Restart/reload the Agent Host and reopen the peer chat so its SDK history is reconstructed.
- 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.
Does this issue occur when all extensions are disabled?: N/A — this is in the core Agent Host peer-chat restore path
dfddfedcf4c9/ PR agentHost: reconcile MCP server enablement #326063Steps to Reproduce:
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:
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) changedCopilotAgentSessionhistory reconstruction to use_storageUriinstead ofsessionUri:_computeMappedEvents()now callsmapSessionEvents(this._storageUri, ...).getSubagentSessions()now usesthis._storageUriasparentSessionStr.For a peer chat,
_storageUriis anahp-chatchannel URI, not a backend session URI.mapSessionEvents()passes that value tobuildSubagentSessionUri(), 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 validcopilotcli:/...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"_meta.subagentChatUriToolResultSubagentContent.resourcecontaining the malformed peer-chat-plus-session-suffix URIThere 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-chatand the encoded path rather than the actual provider/session ID.Suggested fix
sessionUriseparate from storage/routing URI in replay mapping.buildSubagentChatUri(parentSessionUri, toolCallId)for restored subagent tool metadata.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.