fork_session drops parallel tool call branches from JSONL history

Resolved 💬 3 comments Opened Mar 23, 2026 by sahasra098 Closed Mar 27, 2026

Summary

When using fork_session=True with continue_conversation=True via the Claude Agent SDK, the CLI's fork logic drops parallel tool call branches from the previous session's JSONL history. The fork walks the parentUuid chain from leaf to root, producing a linear chain — but parallel tool calls create a tree structure in the JSONL, so sibling branches (tool_use + tool_result pairs not on the main chain path) are silently dropped.

Reproduction

Setup: Use Claude Agent SDK with fork_session=True, continue_conversation=True across two turns.

Turn 1: Ask Claude to write 2 files in parallel. The JSONL correctly records both parallel Write tool_use blocks and their tool_results:

Line 3:  user message: "Write 2 files in parallel"
Line 7:  assistant thinking (uuid=AAA, parent=ZZZ)
Line 8:  assistant tool_use Write file_a.md (uuid=BBB, parent=AAA)
Line 9:  assistant tool_use Write file_b.md (uuid=CCC, parent=BBB)
Line 10: user tool_result for file_a (uuid=DDD, parent=BBB)  ← sibling of line 9
Line 11: user tool_result for file_b (uuid=EEE, parent=CCC)
Line 12: assistant thinking (uuid=FFF, parent=EEE)
Line 13: assistant text "Created both files..." (uuid=GGG, parent=FFF)

Turn 1 JSONL is complete (all tool calls and results present).

Turn 2: Send a follow-up message. The SDK forks the session. The forked JSONL drops the Write file_a tool_use (line 8) and its tool_result (line 10), and rewrites the parentUuid of Write file_b:

Line 7:  assistant thinking (uuid=AAA, parent=ZZZ)
Line 8:  assistant tool_use Write file_b.md (uuid=CCC, parent=AAA)  ← parent CHANGED from BBB to AAA
Line 9:  user tool_result for file_b (uuid=EEE, parent=CCC)
Line 10: assistant thinking (uuid=FFF, parent=EEE)
Line 11: assistant text "Created both files..." (uuid=GGG, parent=FFF)

Turn 2 fork is truncated — missing 1 of 2 parallel Write calls and its result.

Root Cause

The fork logic builds the conversation chain by walking parentUuid from the leaf entry back to the root. This produces a linear chain, but parallel tool calls create a tree:

thinking (AAA)
  └── Write file_a (BBB, parent=AAA)
        ├── Write file_b (CCC, parent=BBB)          ← ON the chain path
        │     └── tool_result file_b (EEE)
        │           └── thinking → final text (leaf)
        └── tool_result file_a (DDD)                  ← SIBLING, dropped

The chain walk goes: leaf → ... → EEE → CCC → BBB → AAA → ...

It visits BBB (Write file_a) as an ancestor of Write file_b, but never visits DDD (tool_result for file_a) because it's a sibling branch, not an ancestor of the leaf.

The fork then drops entries not in the chain and rewrites parentUuids to linearize.

Confirmed using the Python SDK's own _build_conversation_chain() from claude_agent_sdk._internal.sessions — it exhibits the same behavior on synthetic JSONL with parallel tool calls.

Impact

  • Context loss: Follow-up turns lose the content of dropped tool calls. With N parallel calls, N-1 tool_use+tool_result pairs are dropped.
  • Compounds across turns: Each fork drops more siblings from previous turns.
  • Silent: No error or warning. The model just has impoverished context.
  • Scales with parallelism: 2 parallel calls → 1 dropped. 3 → 2 dropped. Heavy parallel workflows (multi-artifact generation) are most affected.

Suggested Fix

When building the forked session, the chain-building logic should preserve all sibling branches at tree nodes, not just the single path from leaf to root. For nodes with multiple children (parallel tool calls), all children and their subtrees should be included and linearized into the fork.

Environment

  • Claude Agent SDK (Python): claude_agent_sdk package
  • Claude CLI: spawned via SDK subprocess
  • SDK options: ClaudeAgentOptions(continue_conversation=True, fork_session=True, ...)

Workaround

Setting fork_session=False while keeping continue_conversation=True avoids the issue — the CLI appends to the existing JSONL instead of forking, preserving the complete tree. The tradeoff is losing per-turn session ID tracking.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗