Skip to content

Session resume loses conversation messages when SubAgent progress chain overtakes main conversation chain #33651

Closed as not planned

Description

@hatawong

Summary

When resuming a session (via --resume, VS Code reload, or process restart), conversation messages can silently disappear from both the UI and LLM context. This happens when a SubAgent's progress messages create a parallel parentUuid branch that has a slightly newer timestamp than the main conversation branch.

Steps to Reproduce

  1. Start a conversation where the main agent dispatches a SubAgent via the Agent tool
  2. The SubAgent runs and generates progress messages (chaining parentUuid from the assistant message that made the Agent tool_use call)
  3. Meanwhile, the main agent receives the SubAgent result and continues the conversation for several turns
  4. The SubAgent's buffered progress messages are flushed to the JSONL after the main conversation messages, so the last progress message has a timestamp slightly newer than the last conversation message
  5. Kill the CLI process (pkill) or restart VS Code
  6. Resume the session

Expected Behavior

All conversation messages (user + assistant) should be visible in the UI and present in the LLM context after resume.

Actual Behavior

The conversation messages between the SubAgent dispatch and the next independently-rooted message are silently lost — gone from both the UI and the LLM context. Only the progress chain path is preserved.

In my case, 28 messages (including substantive assistant analysis and user responses) disappeared after resume with zero indication.

Root Cause

The parentUuid chain reconstruction during session resume picks the most recent non-sidechain message as the tree tip, then traces backwards. Progress messages are not treated as sidechain, so they participate in tip selection.

When the main agent calls a SubAgent, two branches grow from the same parent node:

L_agent (assistant, Agent tool_use)
├── Branch A: tool_result → conversation continues → ... → L_last_conv (ts=T1)
└── Branch B: progress → progress → ... → L_last_progress (ts=T2)
        └── system message → user message → ... (post-resume conversation)

If T2 > T1 (which happens when progress messages are buffered and flushed after conversation messages), the chain reconstruction follows Branch B, and Branch A is completely orphaned.

The post-resume system/user messages chain from the last progress message (Branch B), so once the session is resumed, all subsequent messages are on Branch B. The conversation in Branch A becomes permanently unreachable.

Suggested Fix

Exclude progress messages from tip selection during chain reconstruction. Progress messages don't enter the LLM context by design, so they shouldn't influence which conversation branch is selected.

Something like:

// Current: only excludes sidechain
.filter((P) => !P.isSidechain)

// Fix: also exclude progress
.filter((P) => !P.isSidechain && P.type !== "progress")

Impact

  • Silent data loss: No warning or indication that messages were dropped
  • Not rare: Triggers whenever SubAgent progress flush is delayed relative to main conversation — common with long-running SubAgent tasks
  • Not pkill-specific: Any session reload (VS Code restart, --resume from CLI, window reload) can trigger this if the JSONL tree has this branching structure

Environment

  • Claude Code VS Code extension
  • macOS

Activity

  1. github-actions commented on Mar 12, 2026

    @github-actions

    Found 3 possible duplicate issues:

    1. Conversation history missing on resume (except last message) #24304
    2. [BUG] Conversation chain breaks mid-session - earlier messages lost on resume #21617
    3. Session becomes unresumable after JSONL writer drops assistant entry during parallel tool calls #31328

    This issue will be automatically closed as a duplicate in 3 days.

    • If your issue is a duplicate, please close it and 👍 the existing issue instead
    • To prevent auto-closure, add a comment or 👎 this comment

    🤖 Generated with Claude Code

  2. gvelesandro commented on Mar 13, 2026

    @gvelesandro

    @hatawong I'm researching failures where session resume follows the wrong branch of context after subagent work. Your report is one of the clearest public examples I've seen because the subagent progress chain appears to overtake the main conversation and silently orphan 28 messages.

    If you're open to it, I'd value one short written postmortem here: https://www.agentsneedcontext.com/agent-failure-postmortem

    I'm looking for five specifics: what the main agent and subagent were trying to do, what context disappeared, where that context should have lived, what workaround you used, and what it cost. No pitch, just research.

  3. TeddyRoosevelty commented on Mar 15, 2026

    @TeddyRoosevelty

    I've been having what I'm 90% sure is the same bug independently on Windows/Cursor and have some additional findings that might be useful.

    Added it here because this issue seemed the MOST relevant compared to the others. Let me know if you'd prefer a different issue.

    For me, the "write" path is where the bad data originates

    The forked parentUuid chain isn't just a read-path problem — the incorrect links are written to the JSONL at insert time. In insertMessageChain() (search for sourceToolAssistantUUID in the CLI source), the lastUuid tracker that determines each message's parentUuid is updated for every message type, including progress. So when progress messages are appended, the tracker advances past them, and the next real message gets parented to the last progress message instead of the last real message. So the message paths branch, and any messages that occur before the status return or any in that path get completely hidden from context (kept in the file).

    This means the JSONL on disk has structurally broken links from the moment they're written — it's not just a chain reconstruction issue on reload.

    So this doesn't seem limited to SubAgent progress

    I'm hitting this with hook progress messages from the built-in extension hooks (PreToolUse/PostToolUse for Edit, Write, Read, MultiEdit). The CLI emits progress messages for hook lifecycle events — and appears to do so even when no user-defined hook matches. This means it can trigger on nearly every tool use in a normal conversation, not just SubAgent dispatches.

    Basic Reproduction Steps

    [Using: Use Cursor VS Code Addon]

    New Session
    Say Hi
    Ask it to tell me what is in a file (using the read tool)
    Say thanks
    Reopen the convesation in the conversation

    JSONL repair workaround

    I wrote a repair script that fixes already-broken files. For each non-progress message whose parentUuid points to a progress message, it rewires the parent to the last non-progress message preceding it in file order. Tested across all the affected conversations above. Happy to share if useful to anyone.

  4. skevy commented on Mar 15, 2026

    @skevy

    We're hitting the same bug from the Agent SDK side, triggered by PreToolUse hooks rather than SubAgent progress.

    Repro: query() with streaming input + canUseTool + any PreToolUse hook (even echo noop) + multi-turn conversation. No SubAgents involved.

    import { query } from "@anthropic-ai/claude-agent-sdk";
    
    // PreToolUse hook defined in .claude/settings.local.json:
    // { "hooks": { "PreToolUse": [{ "hooks": [{ "command": "echo noop", "type": "command", "async": true }] }] } }
    
    const response = query({
      prompt: promptStream(), // async iterable, yields two user messages across two turns
      options: {
        canUseTool: async (toolName, input, ctx) => {
          if (toolName === "AskUserQuestion")
            return new Promise((resolve) => { /* resolve later */ });
          return { behavior: "allow", updatedInput: input };
        },
        settingSources: ["user", "project", "local"],
        // ... standard options
      },
    });

    Turn 1: Claude calls ToolSearch → AskUserQuestion → user answers → Claude responds.
    Turn 2: user sends follow-up message.

    The PreToolUse hook progress entries fork from the ToolSearch tool_use node, orphaning the AskUserQuestion round-trip and Claude's response — identical tree shape to the SubAgent case:

    assistant (ToolSearch tool_use)
    ├── Branch A: tool_result → AskUserQuestion → tool_result → response (orphaned)
    └── Branch B: progress(PreToolUse:ToolSearch) → progress(PreToolUse:AskUserQuestion) → Stop → system → user(turn 2)
    

    getSessionMessages follows Branch B, dropping 4-5 messages from Branch A.

    Key isolation: the same query() call without a PreToolUse hook produces a clean chain (0 orphans). Adding even echo noop as an async PreToolUse hook triggers the fork. spawnClaudeCodeProcess is not required — the default spawn reproduces it too.

    This confirms the root cause is the same: progress entries (from any source — SubAgent, PreToolUse hooks, etc.) that branch from a tool_use node and get flushed after the main conversation chain has advanced.

  5. skevy commented on Mar 15, 2026

    @skevy

    Minimal standalone repro: https://github.com/skevy/claude-code-pretooluse-chain-fork-repro

    git clone https://github.com/skevy/claude-code-pretooluse-chain-fork-repro
    cd claude-code-pretooluse-chain-fork-repro
    npm install
    node repro.mjs
    

    Single file, uses only query() + canUseTool + a trivial async PreToolUse hook (echo noop in .claude/settings.json). Two turns. Verifies the JSONL chain and asserts that getSessionMessages drops the AskUserQuestion messages.

  6. junaidtitan commented on Apr 3, 2026

    @junaidtitan

    SubAgent progress chains overtaking the main chain is caused by progress tick messages creating parasitic forks in the parentUuid tree. When the chain walker follows the wrong branch, the main conversation disappears.

    Cozempic v1.4.1 fixes this directly: progress-collapse removes all progress tick messages, and the doctor command's zombie-teams check detects stalled subagent chains. _relink_parent_chain() repairs broken links after any pruning.

    pip install cozempic && cozempic init

    Run cozempic doctor to diagnose subagent chain issues.

  7. khadimhussain0 commented on Apr 7, 2026

    @khadimhussain0

    Adding a CLI-only reproduction on Linux — same root cause, different trigger path.

    Environment: Claude Code CLI 2.1.94, Linux (Debian), Opus 4.6

    Session stats: Single session, 5,760 lines, 2,140 user messages, ~12MB JSONL, 3 compact boundaries, 24 orphan leaf nodes.

    Trigger: In my case the orphan branches come from queue-operation entries (subagent task notifications with
    enqueue/dequeue) combined with multiple compactions. Each compaction resets the main chain but doesn't prune old branches. The
    orphan leaves accumulate at lines scattered across the file (3533, 3916, 4513, 5685, etc.), while the actual latest conversation is
    at line 5750.

    Behavior: --resume and --continue from a second terminal consistently picks an older leaf (around line 3533, between
    compact boundary 1 and 2) instead of the latest leaf (line 5750, after compact boundary 3). The parent chain from the correct leaf
    traces cleanly back to the latest compact_boundary — the data is intact, just unreachable via resume.

    Key difference from other reports: This isn't a VS Code reload or process crash — it happens on a normal --resume from a
    fresh terminal. The JSONL was never interrupted or corrupted. The branching just accumulated naturally over a multi-day session
    with background tasks.

    Confirms this affects CLI on Linux, not just VS Code on macOS. The platform:macos and platform:vscode labels may be too narrow.

  8. github-actions commented on May 20, 2026

    @github-actions

    Closing for now — inactive for too long. Please open a new issue if this is still relevant.

  9. github-actions commented on Jul 10, 2026

    @github-actions

    This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.

  10. locked as resolved and limited conversation to collaborators on Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:corebugSomething isn't workingdata-losshas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOSplatform:vscodeIssue specifically occurs in VS CodestaleIssue is inactive

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions