[Bug] Main agent state lost when resuming session from Claude Agent Panel

Status Open
Reported on v2.1.198
Maintainer reply None cached
Activity 5 comments · opened Jul 2, 2026

Bug Description
this session had a serious bug, the main agent is lost when I get back to this session from claude agent panel.

Environment Info

  • Platform: darwin
  • Terminal: xterm-256color
  • Version: 2.1.198
  • Feedback ID: 35189f78-ab9c-4bb1-b25d-ccd13a496d97

Errors

[]

View original on GitHub ↗

5 Comments

marcoabreu · 1 month ago

Yeah I experience the same - when you accidentally get into the agents view by pressing left and then try to go back into the session, it just re-sends the last prompts and forgets anything it did in between (e.g. active subagents)

HowardXieh · 1 month ago

我已收到您的邮件!会尽快予以回复!

r-po · 1 month ago

Having the same issue, it's frustrating @bcherny could you take a look?

r-po · 1 month ago

Adding root-cause detail after hitting this 5+ times in the last two days (v2.1.201 and also v2.1.198, macOS). I dug into the on-disk transcripts under ~/.claude/projects/<project-dir>/ and the mechanism is visible there.

TL;DR: every subagent/teammate transcript is written as a sibling top-level session file with no parent pointer, and it inherits the parent session's ai-title — so --continue, the --resume picker, and the agent panel can surface a subagent transcript that looks identical to the main session. Once resumed, that file is the whole session; the main agent lives in a different file that nothing references, so there is no way back from inside.

On-disk evidence (a main session + Explore/teammate subagents it spawned):

  • Main and subagent files have identical first-line shape: {"type":"agent-setting","agentSetting":...,"sessionId":"<own id>"}. The subagent files carry no parentSessionId (the head parentUuid is null — that field chains messages, not sessions), and their message entries have isSidechain: false.
  • agentSetting is not a discriminator: teammates spawned as general-purpose agents record "agentSetting": "claude", same as a real main session (only some record "Explore" etc.).
  • The subagents' ai-title records duplicate the parent session's title:
  • main session → {"type":"ai-title","aiTitle":"<parent session title>"}
  • each subagent → the same aiTitle, with only the agent-name records differing (explore-front, explore-backend, …)

That's exactly why the picker shows several identically-titled entries — and the most recently touched one is usually a subagent.

  • The only reliable tell is content-level: a subagent transcript's first user message is the spawn prompt (e.g. starts with <teammate-message teammate_id="team-lead">) instead of a human prompt.

Why the top entry is a subagent: when a session is interrupted or hangs, subagent files finalize after the main file stops being written; and merely opening a transcript from the picker touches its mtime, so a single wrong pick makes that subagent the durable "most recent" entry and --continue keeps landing on it. (Compounding: verifying a candidate with --resume <id> --fork-session -p ... creates yet another sibling file that becomes the new top entry.)

Impact: in every one of my cases the main transcript was intact — all Agent tool_use/tool_result pairs completed, deliverables present. The work isn't lost, it's just unreachable through the UI. Recovery that works every time:

# the main session = the file whose first user message is your own prompt
grep -l "<first words of your original prompt>" ~/.claude/projects/<project-dir>/*.jsonl
claude --resume <main-uuid>

Suggested fixes:

  1. Write a parentSessionId (+ spawn agentName) into subagent transcript headers, and have --continue / the --resume picker / the agent panel exclude them or group them under their parent.
  2. Short term: the picker could de-dupe identically-titled entries in favor of the one whose first message is a human prompt (not a <teammate-message …> / agent spawn block).
  3. Agent panel: when attached to a transcript that has a parent, offer a "back to main agent" action.

Happy to share sanitized transcript headers if useful. Related: #70170 (same dead-end for sessions held as background agents).

gcolonese · 1 month ago

Confirming this bug on Windows (Claude Code v2.1.202, Windows 11), with an additional evidence variant not covered above: metadata-only ghost session files that get re-created after deletion because ~/.claude.json still points at them.

Additional symptoms observed

Every time I run /rename, /color, or /mode from a background-job session, the events land in a different session file than my chat transcript. The result is a "ghost" JSONL file that contains ONLY:

custom-title, agent-name, agent-color, mode, permission-mode, hook_success, attachment

Zero user messages, zero assistant messages, zero tool calls. This is a different failure mode than r-po's subagent-inheritance case — my ghost files have no conversation content at all.

Root cause (Windows-side observation)

~/.claude.json stores lastSessionId per project. Frontend UI commands (rename, color, mode) write to lastSessionId regardless of which session ID owns the current transcript. So:

  • Chat transcript → session A (the active bg job)
  • /rename foo → session B (whatever lastSessionId points at, which can be a stale bg-worker sidecar)

Even after archiving session B's .jsonl, Claude Code silently re-creates it on the next UI command because lastSessionId still references it. The re-created file is 13KB of metadata-only events.

Repro (Windows)

  1. Open Claude Code, start a background job (e.g., a long-running Agent tool call).
  2. From the bg session, run /rename foo and /color orange.
  3. Look at ~/.claude/projects/<hash>/:
  • <active-id>.jsonl — has user/assistant/tool_use events
  • <lastSessionId>.jsonl — contains ONLY the rename/color/mode events
  1. /resume picker shows two entries titled "foo", one with a real conversation, one empty.
  2. Delete the ghost .jsonl. Run /rename foo again → ghost is re-created.

Evidence from my project dir

~/.claude/projects/C--Users-...-Konnect/
├── 16999f09-...jsonl 813 KB full transcript
└── 4178b92d-...jsonl 13 KB metadata-only ghost

.claude.json line ~2087: "lastSessionId": "4178b92d-..." — the pointer that keeps re-summoning the ghost.

Compounding effect with r-po's subagent-inheritance bug

I've spawned three background subagents in this session over the past hour. Each one inherits the session title "MCP to Konnect Migration" (per r-po's finding). Combined with the lastSessionId metadata-ghost mechanism, my /resume picker now shows FIVE entries titled "MCP to Konnect Migration", each landing at a different point in the conversation. The main transcript is intact and reachable via the grep recovery workflow, but the picker experience is unusable.

Suggested fixes (adds to r-po's list)

  1. UI commands (/rename, /color, /mode) must write to the active session ID, not lastSessionId. Update lastSessionId at the same time so it stays in sync.
  2. Do not re-create a .jsonl for a session ID that was deleted from disk — treat disk-absent as authoritative "session gone".
  3. The /resume picker should filter out session files that contain zero user or assistant messages (metadata-only ghosts).