Skip to content

Instantly share code, notes, and snippets.

@gwpl
Last active February 17, 2026 19:00
Show Gist options
  • Select an option

  • Save gwpl/29c1f7a17d69ecb338bf4287d3bbe04e to your computer and use it in GitHub Desktop.

Select an option

Save gwpl/29c1f7a17d69ecb338bf4287d3bbe04e to your computer and use it in GitHub Desktop.
Claude Code CLI: Programmatically Session Branching & Parallel Conversations — Educational guide + runnable experiment script

Claude Code CLI: Session Branching & Parallel Conversations - Programmatically via CLI in non-interactive mode!

Experiment: Exploring how to fork a single Claude Code conversation into multiple parallel branches using --session-id, --resume, and --fork-session.

The Idea: Git-like Branching for AI Conversations

Just like git branch lets you explore multiple code paths from a single commit, Claude Code's session flags let you fork a conversation at a decision point and explore multiple directions independently — each branch remembering the shared context.

         ┌──► Branch A (session $A)
         │
Start ───┼──► Branch B (session $B)
         │
         └──► Branch C (session $C)

Key CLI Flags

Flag Short Description
--session-id <uuid> Assign a specific UUID to the session (must be valid UUID)
--resume <id|name> -r Resume a previous session by ID or name
--fork-session When resuming, create a new session instead of mutating the original
--continue -c Resume the most recent session in the current directory
--model <alias> Override model for this session (haiku, sonnet, opus)
-p / --print Non-interactive "SDK mode": run prompt, print response, exit

Official docs: CLI Reference · Common Workflows – Resume sessions

How --fork-session Works

From the official docs:

--fork-session — When resuming, create a new session ID instead of reusing the original (use with --resume or --continue).

Key behaviors:

  • Must be combined with --resume or --continue — it doesn't work standalone
  • The fork inherits the full transcript and context window from the parent
  • The original session is not modified — it stays at the fork point
  • Forked sessions appear grouped under their root in the /resume picker
  • You can assign a deterministic ID to the fork via --session-id

The Experiment: Proving Context Inheritance

A naive test (fork → tell branch "you are A" → ask "what are you?" → "A") proves nothing — it only shows a session remembers its own messages. To actually prove that --fork-session inherits the parent's context, we need a secret planted in the trunk that is never repeated to any fork.

Method

  1. Trunk: Plant a random secret codeword (e.g. zebra4827)
  2. Fork A/B/C: Ask each to recall the secret — without mentioning it again
  3. Isolation check: Ask Branch A about Branch B's label (should fail)
  4. Resumability: Resume a branch and verify it still knows the secret
# === SETUP ===
SECRET="zebra$(shuf -i 1000-9999 -n 1)"   # Random secret
Start=$(uuidgen); A=$(uuidgen); B=$(uuidgen); C=$(uuidgen)

# === STEP 1: Plant secret in the trunk ===
claude --model haiku --session-id "$Start" \
  -p "The SECRET codeword is: $SECRET. Remember it. Say only: 'Secret received.'"

# === STEP 2: Fork — secret is NOT repeated here ===
claude --model haiku --resume "$Start" --fork-session --session-id "$A" \
  -p 'You are Branch A. What is the SECRET codeword from earlier? Reply with ONLY the codeword.'

claude --model haiku --resume "$Start" --fork-session --session-id "$B" \
  -p 'You are Branch B. What is the SECRET codeword from earlier? Reply with ONLY the codeword.'

claude --model haiku --resume "$Start" --fork-session --session-id "$C" \
  -p 'You are Branch C. What is the SECRET codeword from earlier? Reply with ONLY the codeword.'

# If each fork replies with the correct secret → context inheritance proven!
# The fork prompt never mentioned the secret — it could only know it from the trunk.

# === STEP 3: Verify isolation ===
claude --resume "$A" \
  -p 'What label was Branch B assigned? If you do not know, say "unknown".'
# Should say "unknown" — branches can't see each other

# === STEP 4: Resume and verify persistent memory ===
claude --resume "$C" \
  -p 'What is the SECRET codeword and your branch label?'
# Should recall both the trunk secret AND its own label

Why This Proves It

  • The secret exists only in the trunk's conversation history
  • Fork prompts never mention the secret — if a fork knows it, the only explanation is that --fork-session copied the trunk's full context
  • Branch isolation confirms forks don't cross-contaminate

The Script

See claude-code-session-branching-experiment.sh for a copy-paste-ready script that reproduces this experiment.

Practical Applications

1. A/B Testing Approaches

# Build shared context about a bug
claude --session-id "$BASE" -p "Analyze the auth timeout bug in src/auth.ts. Don't fix yet."

# Fork: try two different fix strategies
claude -r "$BASE" --fork-session --session-id "$FIX_A" -p "Fix using retry with exponential backoff"
claude -r "$BASE" --fork-session --session-id "$FIX_B" -p "Fix using connection pooling"

# Review each
claude -r "$FIX_A"
claude -r "$FIX_B"

2. Parallel Task Execution (Background)

# Shared analysis
claude --session-id "$BASE" -p "Read the codebase and summarize the architecture."

# Fork N tasks in parallel (background)
claude -r "$BASE" --fork-session -p "Write unit tests for auth module" &
claude -r "$BASE" --fork-session -p "Write unit tests for payment module" &
claude -r "$BASE" --fork-session -p "Write unit tests for user module" &
wait

3. Interactive Exploration Tree

# Start interactive, reach a decision point, then Ctrl+C
claude --session-id "$ROOT"
# ...conversation about refactoring approach...

# Fork interactively to try each approach
claude -r "$ROOT" --fork-session   # explore approach 1
claude -r "$ROOT" --fork-session   # explore approach 2

Session Storage

Sessions are stored locally at:

~/.claude/projects/<cwd-path-encoded>/

Each session is a JSONL transcript. Forked sessions reference their parent, which is why the /resume picker can group them together.

Tips

  • Name sessions with /rename inside interactive mode for easier discovery
  • Use --model haiku for cheap/fast exploration branches
  • Combine with --output-format json for programmatic pipelines
  • --no-session-persistence (print mode only) to create throwaway branches
  • Press Esc+Esc inside interactive mode to rewind to earlier messages (creates a fork)
  • The /resume picker groups forked sessions — press to expand children

Related Resources

#!/usr/bin/env bash
# ============================================================================
# Claude Code Session Branching Experiment
# ============================================================================
# PROVES that --fork-session inherits the parent session's full context.
#
# Method:
# 1. Create a trunk session with a random SECRET word
# 2. Fork 3 branches — none are told the secret again
# 3. Each fork is asked to recall the secret from the trunk
# 4. Each fork is also given a unique LABEL (A/B/C)
# 5. Cross-check: ask each branch for BOTH the secret AND its own label
# 6. Verify branches are isolated: ask A about B's label (should fail)
#
# If forks can recall the trunk's secret, context inheritance is proven.
# If forks can't see each other's labels, isolation is proven.
#
# Prerequisites:
# - Claude Code CLI installed (npm install -g @anthropic-ai/claude-code)
# - Authenticated (claude auth login)
# - uuidgen available (standard on macOS/Linux)
#
# Optional:
# - claude-history-query (for pretty conversation display at the end)
# Install from: https://github.com/CLIAI/claude-history-query
#
# Usage:
# chmod +x claude-code-session-branching-experiment.sh
# ./claude-code-session-branching-experiment.sh
# ============================================================================
set -euo pipefail
# --- Configuration ---
MODEL="${CLAUDE_MODEL:-haiku}" # Use haiku by default (fast & cheap)
# Override: CLAUDE_MODEL=sonnet ./script.sh
# Generate a random secret word to plant in the trunk
SECRET="zebra$(shuf -i 1000-9999 -n 1)" # e.g. "zebra4827" — unlikely to be guessed
echo "========================================================="
echo " Claude Code Session Branching: Context Inheritance Proof"
echo "========================================================="
echo ""
echo "Model: $MODEL"
echo "Secret: $SECRET (planted in trunk, never repeated to forks)"
echo ""
# --- Step 1: Generate deterministic session UUIDs ---
echo "--- Step 1: Generating session UUIDs ---"
Start=$(uuidgen)
A=$(uuidgen)
B=$(uuidgen)
C=$(uuidgen)
echo " Trunk session: $Start"
echo " Branch A: $A"
echo " Branch B: $B"
echo " Branch C: $C"
echo ""
# --- Step 2: Create the trunk with a SECRET ---
echo "--- Step 2: Creating trunk session with secret ---"
TRUNK_PROMPT="You are in a branching experiment. The SECRET codeword is: $SECRET. Remember it. Acknowledge by saying only: 'Secret received.'"
echo " Prompt: (contains secret '$SECRET')"
echo ""
TRUNK_RESPONSE=$(set -x; claude --model "$MODEL" \
--session-id "$Start" \
-p "$TRUNK_PROMPT")
echo " Trunk response: $TRUNK_RESPONSE"
echo ""
# --- Step 3: Fork into three parallel branches ---
# IMPORTANT: None of the fork prompts mention the secret!
# Each fork only gets a label (A/B/C) and is asked to recall the secret.
echo "--- Step 3: Forking 3 branches (secret NOT repeated) ---"
echo ""
TMPDIR_EXP=$(mktemp -d)
echo " Forking A, B, C in parallel..."
# Each subshell: set -x traces the command to stderr (→ trace file),
# claude stdout goes to response file. This keeps traces clean.
(set -x; claude --model "$MODEL" \
--resume "$Start" --fork-session --session-id "$A" \
-p 'You are now Branch A. What is the SECRET codeword from earlier? Reply with ONLY the codeword, nothing else.') \
> "$TMPDIR_EXP/resp_a.txt" 2> "$TMPDIR_EXP/trace_a.txt" &
PID_A=$!
(set -x; claude --model "$MODEL" \
--resume "$Start" --fork-session --session-id "$B" \
-p 'You are now Branch B. What is the SECRET codeword from earlier? Reply with ONLY the codeword, nothing else.') \
> "$TMPDIR_EXP/resp_b.txt" 2> "$TMPDIR_EXP/trace_b.txt" &
PID_B=$!
(set -x; claude --model "$MODEL" \
--resume "$Start" --fork-session --session-id "$C" \
-p 'You are now Branch C. What is the SECRET codeword from earlier? Reply with ONLY the codeword, nothing else.') \
> "$TMPDIR_EXP/resp_c.txt" 2> "$TMPDIR_EXP/trace_c.txt" &
PID_C=$!
wait $PID_A $PID_B $PID_C
# Show the traced commands, then the responses
for label in a b c; do
echo " $(cat "$TMPDIR_EXP/trace_${label}.txt")"
done
RESP_A=$(cat "$TMPDIR_EXP/resp_a.txt")
RESP_B=$(cat "$TMPDIR_EXP/resp_b.txt")
RESP_C=$(cat "$TMPDIR_EXP/resp_c.txt")
echo " Branch A recalls secret: $RESP_A"
echo " Branch B recalls secret: $RESP_B"
echo " Branch C recalls secret: $RESP_C"
echo ""
# --- Step 4: Verify context inheritance ---
echo "--- Step 4: Verify context inheritance ---"
PASS=0
FAIL=0
for label_var in A B C; do
resp_var="RESP_$label_var"
resp="${!resp_var}"
if echo "$resp" | grep -qi "$SECRET"; then
echo " [PASS] Branch $label_var correctly recalled secret '$SECRET'"
PASS=$((PASS + 1))
else
echo " [FAIL] Branch $label_var responded '$resp' (expected '$SECRET')"
FAIL=$((FAIL + 1))
fi
done
echo ""
# --- Step 5: Verify branch isolation ---
echo "--- Step 5: Verify branch isolation ---"
echo " Asking Branch A what label Branch B has (should NOT know)..."
ISOLATION_TEST=$(set -x; claude --model "$MODEL" \
--resume "$A" \
-p 'What label was Branch B assigned? If you do not know, say "unknown". Reply in one word.')
echo " Branch A says about B: $ISOLATION_TEST"
if echo "$ISOLATION_TEST" | grep -qi "unknown\|don.t know\|no information\|not sure\|cannot"; then
echo " [PASS] Branch A cannot see Branch B's context (isolation confirmed)"
PASS=$((PASS + 1))
else
echo " [INFO] Branch A responded: '$ISOLATION_TEST' (may have inferred from naming pattern)"
fi
echo ""
# --- Step 6: Verify resumability with memory ---
echo "--- Step 6: Resume branches and verify they remember both secret AND label ---"
echo ""
echo " Resuming Branch C..."
RESUME_C=$(set -x; claude --model "$MODEL" \
--resume "$C" \
-p 'Reply with exactly two things separated by a comma: (1) the SECRET codeword, (2) your branch label letter.')
echo " Branch C says: $RESUME_C"
if echo "$RESUME_C" | grep -qi "$SECRET"; then
echo " [PASS] Resumed Branch C still remembers the trunk secret"
PASS=$((PASS + 1))
else
echo " [FAIL] Resumed Branch C forgot the trunk secret"
FAIL=$((FAIL + 1))
fi
echo ""
# --- Summary ---
echo "========================================================="
echo " RESULTS: $PASS passed, $FAIL failed"
echo "========================================================="
echo ""
if [ "$FAIL" -eq 0 ]; then
echo " All checks passed! Fork-session correctly inherits"
echo " parent context and branches are isolated from each other."
else
echo " Some checks failed. See details above."
fi
echo ""
echo "To interactively resume any branch:"
echo " claude --resume $A # Branch A"
echo " claude --resume $B # Branch B"
echo " claude --resume $C # Branch C"
echo ""
echo "To see all sessions (forked ones grouped under trunk):"
echo " claude --resume"
# --- Step 7: Show conversation transcripts (if claude-history-query is available) ---
echo ""
echo "========================================================="
echo " Conversation Transcripts"
echo "========================================================="
echo ""
if command -v claude-history-query &>/dev/null; then
for label in Start A B C; do
session_var="$label"
session_id="${!session_var}"
echo ""
echo "---------------------------------------------------------"
echo " Session: $label ($session_id)"
echo "---------------------------------------------------------"
(set -x; claude-history-query show "$session_id") || echo " (could not retrieve session $session_id)"
echo ""
done
else
echo " claude-history-query is not installed."
echo " To view formatted conversation transcripts, install it from:"
echo ""
echo " https://github.com/CLIAI/claude-history-query"
echo ""
echo " Then re-run, or manually inspect sessions with:"
echo ""
echo " claude-history-query show $Start # Trunk"
echo " claude-history-query show $A # Branch A"
echo " claude-history-query show $B # Branch B"
echo " claude-history-query show $C # Branch C"
fi
# Cleanup temp files
rm -rf "$TMPDIR_EXP"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment