microsoft / microsoft/vscode

Git multi-file diff refresh steals keyboard focus from the terminal

Open
#336,363 0 comments 0 reactions 1 assignee Claimed by @hediet View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

Does this issue occur when all extensions are disabled?: **Yes**. Reproduced using a separate user-data directory and `--disable-extensions`, with the built-in Git integration enabled.

- VS Code Version: **1.138.0**, commit `7debcd0e2acdea1c52de81bf9ee1620444407dda` (also observed in 1.133.0).
- OS Version: **macOS 26.6.2, arm64**.
- Latest Insiders: not tested.

## Summary

When an already-open multi-file Git diff refreshes after a change to its file list, it can take keyboard focus away from the integrated terminal. Subsequent typing goes into the diff instead of the terminal. This happens with both **Git: Staged Changes** and **Git: Changes** (the unstaged multi-file view), without Copilot or third-party extensions.

The reliable triggers are an empty list becoming populated again, or the active diff file being replaced. Merely adding another file while the active item remains does not necessarily trigger it.

## Steps to reproduce: staged multi-file view

1. Create a disposable Git repository with a committed text file, then modify and stage it. For example, in a new empty folder:

```sh
git init
printf 'baseline\n' > first.txt
git add first.txt
git commit -m 'Add baseline'
printf 'changed\n' >> first.txt
git add first.txt
```

2. Open the folder in VS Code. In Source Control, click **Open Staged Changes** on the **Staged Changes group header**. This opens the multi-file **Git: Staged Changes** tab, not an individual file's diff. One changed file is sufficient.
3. Leave that tab open and click into the integrated terminal. The reported layout has the terminal on the right; an isolated reproduction also used a right-positioned panel.
4. Run `git reset -- first.txt`. Wait for the open diff to show **No Changed Files**. Leave the tab open.
5. Ensure the terminal has focus, then run `git add first.txt` and continue typing as the staged list refreshes.

**Expected:** The staged diff updates without moving keyboard focus. Typing continues in the terminal.

**Actual:** The diff selects its first change and takes keyboard focus from the terminal. Typing no longer reaches the terminal and may attempt to edit the read-only staged diff.

## Unstaged multi-file view

Starting with the modified file unstaged (`git reset -- first.txt`), open **Open Changes** on the **Changes group header** in Source Control. This opens **Git: Changes**. Do not use the identically named single-file command or click an individual file row.

1. Focus the terminal and run `git add first.txt`, leaving the now-empty **Git: Changes** tab open.
2. Once it shows **No Changed Files**, ensure the terminal has focus and run `git reset -- first.txt`.
3. When the unstaged change reappears, the multi-file diff takes keyboard focus in the same way.

The issue also reproduced in both multi-file views when the active file left the list and a different file became the remaining change.

## Verification and scope

The focus theft was reproduced in isolated live windows running the original, unpatched 1.138.0 workbench code with extensions disabled. The active DOM element moved from the terminal's `xterm-helper-textarea` into the multi-diff editor's `native-edit-context`.

For deterministic automated checks, Git status refresh was explicitly requested with **Git: Refresh** after the index operation, then the tests waited for visible diff content to finish updating. This avoided filesystem-watcher latency in the test folder (on Google Drive). The original user-facing symptom occurs after running Git commands in the terminal. The controlled tests do not establish whether the filesystem location affects refresh timing.

Standalone single-file **Index** and **Working Tree** diffs did **not** reproduce the problem in the tested scenarios: viewed-content updates, another file entering the change list, the viewed file leaving/re-entering the list, and subsequent terminal typing. Those tests waited for both rendered diff sides to update and recorded no transient focus events into the standalone diff. `git.closeDiffOnOperation` was left at its default.

## Possible cause

In [MultiDiffEditorWidgetImpl](https://github.com/microsoft/vscode/blob/7debcd0e2acdea1c52de81bf9ee1620444407dda/src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts), the automatic first-change selection calls `_navigateToChange('next', !this._preserveFocusOnLoad)` when the loaded model has items but no active diff item. A file-list refresh can reach that state after the user has moved focus elsewhere, but `_preserveFocusOnLoad` still reflects the original editor-open options. Persisted active-item restoration uses the same flag to decide whether to focus.

After reproducing with the original application code, a reversible local diagnostic patch added a current-focus containment check to those two automatic paths. The previously failing staged and unstaged multi-file cases then retained terminal focus; clicking into the diff and explicit next/previous-change navigation remained functional in the tested cases. This is supporting evidence, not a claim that the guard covers every initial-open, restoration, or accessibility case. The application was unpatched when reproducing the bug.

I searched open and closed issues before filing. Related reports such as #205567 (focus on returning to a tab), #296087 (a different Copilot CLI session opening a diff), and #308151 (rendering corruption) appear to describe different triggers.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.