microsoft / microsoft/vscode

fix: prevent TreeError when focusing agent sessions list during async refresh (fixes #330875)

Open
#330,880 0 comments 0 reactions 0 assignees View on GitHub
agentic-workflows errors-fix
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

### Summary

`TreeError [AgentSessionsView] Tree element not found: [object Object]` is thrown from the Agent Sessions view when the list is focused while its async data tree is mid-refresh. It surfaced as a new stable anomaly in 1.133.0 (absent in 1.132.1), affecting ~157 users.

`AgentSessionsControl.focus()` calls `focusFirst()`, which sets focus by **list-view index**. During an async `updateChildren()` refresh the list view can momentarily reference an element that is not present in the compressed tree model's node map. Setting focus to that stale index synchronously fires `onDidChangeFocus`; the workbench list listener (`updateCollapseContextKeys` in `listService.ts`) then calls `tree.getNode(focus)`, which reaches `CompressedObjectTreeModel.getCompressedNode` and throws because the element is not in `this.nodes`.

The `try/catch` previously wrapping `focusFirst()` could never catch this: `Emitter._deliver` wraps every listener invocation in its own `try/catch` and reroutes exceptions to `onUnexpectedError` (the telemetry pipeline). The throw happens out-of-band during event delivery, not in the synchronous `focusFirst()` return path — so the guard was dead code for this crash.

Fixes microsoft/vscode\#330875
Recommended reviewer: `@bpasero`

### Culprit Commit

The exact regressing commit could not be pinned via `git merge-base`/`git show` because arbitrary-SHA object access is blocked in this environment (blobless/partial clone with no fetch credentials). Reasoning from local blame: the `focus()` → `focusFirst()` path with an ineffective `try/catch` was introduced by `150dcd72ba5` (BeniBenj, 2026-05-04, "fixes \#313042"), on top of the async list/update foundation authored by Benjamin Pasero (`b1009c98bb42`). The bucket is a genuine new anomaly in the 1.132.1 → 1.133.0 range, consistent with recent churn in this file (e.g. `406f70688322` update/pause logic).

### Code Flow

```mermaid
flowchart TD
A["AgentSessionsControl.focus()"] --> B["sessionsList.focusFirst()"]
B --> C["listWidget.focusNth(0)"]
C --> D["setFocus([index]) by list-view index"]
D --> E["abstractTree.setFocus fires onDidChangeFocus"]
E --> F["Emitter._deliver wraps listener in try/catch"]
F --> G["listService updateCollapseContextKeys()"]
G --> H["tree.getNode(focus)"]
H --> I["CompressedObjectTreeModel.getCompressedNode"]
I --> J["nodes.get(element) is undefined"]
J --> K["throw TreeError 'Tree element not found'"]
F --> L["exception routed to onUnexpectedError -> telemetry"]
K --> L
```

### Affected Files

- `src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts` — the producer/trigger. `focus()` sourced focus by stale list-view index.
- `src/vs/platform/list/browser/listService.ts` (crash consumer, shared base — not modified): `updateCollapseContextKeys` calls `tree.getNode(focus)`.
- `src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts` (throw site, shared base — not modified): `getCompressedNode` throws when the element is absent from `nodes`.
- `src/vs/base/common/event.ts` (not modified): `_deliver` reroutes listener exceptions to telemetry, which is why the old `try/catch` was ineffective.

### Repro Steps

1. Open the Agent Sessions view with sessions present.
2. Trigger an async list refresh (`update()` → `updateChildren()`), e.g. via session activity or repository label recomputation.
3. While the refresh is in-flight, focus the view (e.g. active editor change / view focus), invoking `focus()` → `focusFirst()`.
4. Focus lands on a list-view index whose element is momentarily absent from the tree model; `onDidChangeFocus` → `getNode` throws `TreeError [AgentSessionsView] Tree element not found`, reported to telemetry.

### How the Fix Works

**Chosen approach** (`agentSessionsControl.ts`): Replace `focusFirst()` with focus sourced from the tree model. The new `focus()` reads the first top-level element from `sessionsList.getNode(this.agentSessionsService.model).children[0]`, re-checks it with `hasNode()`, and only then calls `setFocus([firstElement])`. This fixes the problem at the data producer rather than the crash site: focus can now only ever point at an element the tree model actually holds, so the `onDidChangeFocus` listener's `getNode(focus)` cannot resolve a stale element. This mirrors the safe pattern already used elsewhere in the same file (`revealAndFocusActiveEditorSession` guards `setFocus` with `hasNode`, and `getNode(model)` is used to enumerate top-level children).

The ineffective `try/catch` is removed because it was dead code — exceptions thrown during synchronous event delivery are caught by `Emitter._deliver` and routed to telemetry, never back to the `focusFirst()` caller. Keeping it would falsely imply the error was handled.

After this change, `agentSessionsControl.ts` `focus()` cannot produce a focus element absent from the tree model, because the element is read from the model's own children and re-validated with `hasNode()` immediately before `setFocus`, so the `onDidChangeFocus` → `getNode` path always resolves a present node.

**Alternatives considered**:
- Guarding `getNode`/`getCompressedNode` at the crash site — rejected: it is shared base code used by every workbench tree, and would mask the producing desync instead of fixing it.
- Expanding or keeping the `try/catch` around `focusFirst()` — rejected: it cannot catch the out-of-band exception delivered via `Emitter._deliver`, so it hides nothing and fixes nothing.

### Recommended Owner

`@bpasero` — authored the Agent Sessions async list/update foundation (`b1009c98bb42`, and recent sessions work such as `b0f253eec7` on 2026-04-30) and is an active core maintainer with write access. He owns the surrounding update/refresh machinery that this fix coordinates with.

> Generated by [errors-fix](https://github.com/microsoft/vscode-engineering/actions/runs/31814659004) · opus48 · 627.5 AIC · ⌖ 11.9 AIC · ⊞ 18.6K · [◷](https://github.com/search?q=repo%3Amicrosoft%2Fvscode+%22gh-aw-workflow-id%3A+errors-fix%22&type=pullrequests)

---

> [!NOTE]
> This was originally intended as a pull request, but PR creation failed. The changes have been pushed to the branch [`fix/agent-sessions-focus-tree-not-found-5fcce589c6fc07cd`](https://github.com/vscodebot-pr/vscode/tree/fix/agent-sessions-focus-tree-not-found-5fcce589c6fc07cd).
>
> **Original error:** ERR_API: [2026-08-14T15:54:46.444Z] create pull request in microsoft/vscode failed (attempt 1)

Original error: Validation Failed: {"resource":"PullRequest","code":"custom","field":"fork_collab","message":"fork_collab Fork collab can't be granted by someone without permission"} - https://docs.github.com/rest/pulls/pulls#create-a-pull-request
Retryable: false
Suggestion: This error cannot be resolved by retrying. Please check the error details and fix the underlying issue.

To create the pull request manually:

```sh
gh pr create --title "fix: prevent TreeError when focusing agent sessions list during async refresh (fixes #330875)" --base main --head vscodebot-pr:fix/agent-sessions-focus-tree-not-found-5fcce589c6fc07cd --repo microsoft/vscode
```

Show patch preview (46 of 46 lines)

```diff
From 544dff38dfe5197a5ff786b80eb5de832c9b0352 Mon Sep 17 00:00:00 2001
X-GH-AW-Base-Commit: 6ebcc0a377ae04e130212f34df551c8340cf966a
From: "github-actions[bot]"
Date: Fri, 14 Aug 2026 15:47:38 +0000
Subject: [PATCH] fix: prevent TreeError when focusing agent sessions list
during async refresh (fixes #330875)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../agentSessions/agentSessionsControl.ts | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts
index 39cd98139ff..a854bb27d8a 100644
--- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts
+++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts
@@ -860,12 +860,19 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
focus(): void {
this.sessionsList?.domFocus();

- try {
- if ((this.sessionsList?.getFocus().length ?? 0) === 0) {
- this.sessionsList?.focusFirst();
- }
- } catch {
- // Tree model may be temporarily inconsistent during async refresh.
+ if (!this.sessionsList || this.sessionsList.getFocus().length > 0) {
+ return;
+ }
+
+ // Focus the first top-level element sourced from the tree model rather than calling
+ // focusFirst(), which sets focus by list-view index. During an async refresh the list
+ // view can reference an element that is momentarily absent from the tree model, which
+ // makes the onDidChangeFocus listener throw "Tree element not found" when it resolves
+ // the focused element. Re-check the element via hasNode() so focus only ever points at
+ // an element the model still holds.
+ const firstElement = this.sessionsList.getNode(this.agentSessionsService.model).children[0]?.element;
+ if (firstElement && this.session
... (truncated)
```

Contributor guide

Open the contributing guide

Research direction

Start in src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts at AgentSessionsControl.focus(), then read the refresh path through updateChildren() and the related tree handling in listService.ts. Reproduce by focusing the Agent Sessions view during an async refresh; done means the refresh no longer reports TreeError and focus remains valid.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.