Describe the bug
When two Claude Code sessions are open in the same VS Code window and /clear is run in one of them, the mapping between agent characters and terminals desyncs. The character that visually shows the busy/active status (spin-up indicators, tool overlay, animations) and the character shown as idle become mis-mapped to terminals: clicking the busy-looking character focuses the cleared/idle terminal, and clicking the idle-looking character focuses the terminal that is actually running agents.
In other words, the displayed status appears correct for the world (one agent IS active, one IS idle), but the click target for each character is swapped — the character is bound to the other session's terminal.
This looks like a more specific, reliably reproducible variant of the README's known limitation:
Agent-terminal sync — the way agents are connected to Claude Code terminal instances is not super robust and sometimes desyncs, especially when terminals are rapidly opened/closed or restored across sessions.
…but the trigger here is /clear (which mints a new session_id and a new .jsonl file inside Claude Code without touching the VS Code terminal), not terminal open/close.
Steps to reproduce
- Open the Pixel Agents panel.
- Spawn (or adopt) two Claude Code sessions in the same VS Code window — call them Session A and Session B. Both characters appear in the office.
- In Session A, kick off some real work (e.g. ask Claude to run a few tools / a long task) so its character clearly shows the busy/spin-up state.
- In Session B, run
/clear. Don't send any further input.
- Observe the office: one character shows the busy/tool-active overlay, the other looks idle. The visual states themselves are correct.
- Click the busy-looking character.
Expected behavior
The terminal that is actually running agents (Session A) is revealed/focused.
Actual behavior
The cleared terminal (Session B) is revealed/focused. Clicking the idle-looking character reveals the busy Session A terminal. The two characters' click targets are effectively swapped.
Hypothesis (subsystem at fault)
Looking at the bundled dist/extension.js, the click handler is the focusAgent message: it does agents.get(s.id).terminalRef.show(). So the agent id arriving from the webview is being resolved against an agents map whose terminalRef (or sessionId → agentId mapping) has been crossed during the SessionStart-after-/clear path.
Suspicious areas:
onSessionClear updates agent.sessionId to the new session id but resolves the target agent by lookup over existing agents. If two agents share the same projectDir (same workspace), the lookup keyed off transcript_path dirname / cwd can match the wrong agent record and rebind that record's sessionId to the new (cleared) session — leaving the OTHER agent record still believing it owns the new session via its old sessionId. The end result is two agent records whose sessionIds are correct but whose terminalRefs are now pointing at each other's terminals.
- The "external session detected" branch (
[...agents.values()].find(m => m.jsonlFile === e)) plus the auto-discovered branch (p.sessionId === s.session_id) both run on SessionStart and can each match a different agent record for the same incoming event when a /clear produces a brand-new .jsonl with the new session_id arriving on the hook before the file watcher sees the new file.
onDidChangeActiveTerminal only updates activeAgentId from terminalRef === s; it never re-validates that the agent's stored sessionId still belongs to that terminal, so once swapped the binding never self-heals.
A test that would isolate this: after step 4, log [...agents.values()].map(a => ({id: a.id, sessionId: a.sessionId, term: a.terminalRef?.name, jsonl: path.basename(a.jsonlFile)})) and check whether the two records' terminalRef.name and jsonlFile are crossed.
Workaround for users: closing and reopening the affected terminal forces a re-bind, which restores the correct mapping until the next /clear.
Extension version
1.3.0
VS Code version
VS Code 1.105.x on macOS (Darwin arm64 25.4.0 / Apple Silicon)
Claude Code CLI version
2.1.x (recent)
Operating System
macOS
Logs
Not collected for this report; happy to provide a [Pixel Agents] console capture across the /clear boundary if useful — let me know what level of detail helps.
Filed via Claude Code on behalf of a user.
Describe the bug
When two Claude Code sessions are open in the same VS Code window and
/clearis run in one of them, the mapping between agent characters and terminals desyncs. The character that visually shows the busy/active status (spin-up indicators, tool overlay, animations) and the character shown as idle become mis-mapped to terminals: clicking the busy-looking character focuses the cleared/idle terminal, and clicking the idle-looking character focuses the terminal that is actually running agents.In other words, the displayed status appears correct for the world (one agent IS active, one IS idle), but the click target for each character is swapped — the character is bound to the other session's terminal.
This looks like a more specific, reliably reproducible variant of the README's known limitation:
…but the trigger here is
/clear(which mints a new session_id and a new.jsonlfile inside Claude Code without touching the VS Code terminal), not terminal open/close.Steps to reproduce
/clear. Don't send any further input.Expected behavior
The terminal that is actually running agents (Session A) is revealed/focused.
Actual behavior
The cleared terminal (Session B) is revealed/focused. Clicking the idle-looking character reveals the busy Session A terminal. The two characters' click targets are effectively swapped.
Hypothesis (subsystem at fault)
Looking at the bundled
dist/extension.js, the click handler is thefocusAgentmessage: it doesagents.get(s.id).terminalRef.show(). So the agent id arriving from the webview is being resolved against anagentsmap whoseterminalRef(orsessionId → agentIdmapping) has been crossed during the SessionStart-after-/clearpath.Suspicious areas:
onSessionClearupdatesagent.sessionIdto the new session id but resolves the target agent by lookup over existing agents. If two agents share the sameprojectDir(same workspace), the lookup keyed offtranscript_pathdirname /cwdcan match the wrong agent record and rebind that record'ssessionIdto the new (cleared) session — leaving the OTHER agent record still believing it owns the new session via its oldsessionId. The end result is two agent records whosesessionIds are correct but whoseterminalRefs are now pointing at each other's terminals.[...agents.values()].find(m => m.jsonlFile === e)) plus the auto-discovered branch (p.sessionId === s.session_id) both run on SessionStart and can each match a different agent record for the same incoming event when a/clearproduces a brand-new.jsonlwith the new session_id arriving on the hook before the file watcher sees the new file.onDidChangeActiveTerminalonly updatesactiveAgentIdfromterminalRef === s; it never re-validates that the agent's storedsessionIdstill belongs to that terminal, so once swapped the binding never self-heals.A test that would isolate this: after step 4, log
[...agents.values()].map(a => ({id: a.id, sessionId: a.sessionId, term: a.terminalRef?.name, jsonl: path.basename(a.jsonlFile)}))and check whether the two records'terminalRef.nameandjsonlFileare crossed.Workaround for users: closing and reopening the affected terminal forces a re-bind, which restores the correct mapping until the next
/clear.Extension version
1.3.0
VS Code version
VS Code 1.105.x on macOS (Darwin arm64 25.4.0 / Apple Silicon)
Claude Code CLI version
2.1.x (recent)
Operating System
macOS
Logs
Not collected for this report; happy to provide a
[Pixel Agents]console capture across the/clearboundary if useful — let me know what level of detail helps.Filed via Claude Code on behalf of a user.