Skip to content

[Bug]: Clicking agent character opens wrong terminal after /clear in another session #251

Description

@JrodObrien

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

  1. Open the Pixel Agents panel.
  2. 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.
  3. 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.
  4. In Session B, run /clear. Don't send any further input.
  5. Observe the office: one character shows the busy/tool-active overlay, the other looks idle. The visual states themselves are correct.
  6. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions