Claude: forking a chat silently drops the source provider context
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
Forking a chat against the Claude provider silently produces a chat with **no provider context**. The forked chat looks correct at the AHP level — the source turn is seeded into its transcript — but the model receives none of the source history, so it cannot answer anything about the source conversation. No error reaches the client.
The same operation works correctly for Copilot, so this is provider-specific rather than a gap in the shared fork contract.
### Repro
Enable the E2E gate and record against the live SDK:
```bash
# src/vs/platform/agentHost/test/node/e2e/providers/claudeAgentHostE2E.integrationTest.ts
- supportsChatForkE2E: false,
+ supportsChatForkE2E: true,
```
```bash
AGENT_HOST_UPDATE_SNAPSHOTS=1 ./scripts/test-integration.sh --run \
src/vs/platform/agentHost/test/node/e2e/providers/claudeAgentHostE2E.integrationTest.ts \
--grep "forked peer chat inherits source history through the provider"
```
The scenario: ask the source chat to remember `FORKCODE`, fork from that turn, then ask the fork what the code word was.
### Result
Only the AHP-level assertion passes:
```
seededMessages: ok source turn is present in the forked chat
requestHasPriorUserMessage: FAIL model request has no source user turn
requestHasPriorAssistantMessage: FAIL model request has no source reply
responseHasCodeWord: FAIL model cannot recall FORKCODE
```
For comparison, the committed Copilot capture for the identical test shows the fork's request carrying the full inherited history:
```yaml
messages:
- role: user
content: Remember the code word FORKCODE. Reply exactly "ready".
- role: assistant
content: ready
- role: user
content: What code word did I ask you to remember? Reply with only the code word.
response:
content: FORKCODE
```
### Cause
`resolveForkAnchorUuid` (`src/vs/platform/agentHost/node/claude/claudeReplayMapper.ts`) matches the requested turn id against **Claude SDK envelope uuids**, so it resolves only when the AHP turn id happens to *be* an SDK uuid:
```
resolveForkAnchorUuid(messages, 'u1') -> 'a1' SDK uuid, resolves
resolveForkAnchorUuid(messages, 'fork-source') -> undefined client turn id, never resolves
```
AHP lets a client choose its own turn id when dispatching a turn, and Copilot honors that. For such an id the anchor never resolves; `_forkChat` (`claudeAgent.ts`) logs a warning, returns `undefined`, and `createChat` continues with a fresh chat.
Note the fork path never reaches `forkSession`, so no `Invalid upToMessageId` error is involved — that string appears only in a unit-test stub and inside the SDK, and previously misled the notes on this defect.
### Why this is a defect rather than a limitation
The silent part is the problem. `createChat` reports success, the forked chat's state looks correctly seeded, and only the model's behavior reveals that the context was dropped. A client cannot detect this.
That is the case the design principles single out:
> **Fail explicitly for contract violations.** Required AHP contracts should fail with typed errors or visible diagnostics when violated. Silent fallbacks are for intentional optional behavior, not for broken required behavior.
Either the anchor should resolve for a client-assigned turn id (matching Copilot), or the fork should fail with a typed error instead of quietly degrading.
### Impact
- `forked peer chat inherits source history through the provider` — disabled for Claude via `supportsChatForkE2E: false`.
- `unknown-turn fork does not inherit source provider context` — asserts the *correct* behavior for an unresolvable anchor; shares the gate only because both are `forkProviderTest`s, and should pass once the resolvable case works.
- `side chat receives bounded source context without copied history` — side chats anchor on a source turn and hit the same resolution path, so they silently fall back to an injected `` preamble. Its capture cannot be re-recorded, and is tracked in `STALE_RECORDED_REQUEST_EXCEPTIONS`.
Symptom and cause are recorded in `src/vs/platform/agentHost/test/node/e2e/KNOWN_ISSUES.md` under *Claude provider-context fork*.
(Written by Copilot)
Contributor guide
Assessment
This issue has not been assessed yet.