chat: stop agent host config update loop across windows#320015
Merged
Conversation
The local agent host's root config is shared across all VS Code windows. AgentHostTerminalContribution re-pushed its own managed values (defaultShell, disableCustomTerminalTool) on every rootState.onDidChange, including value changes made by *other* windows. Two windows with different terminal settings would therefore ping-pong RootConfigChanged actions forever (~8000 dispatches in 25s in the captured logs). Fix: only re-push when a managed key's *schema* first appears (host hydration), not on value-only changes from other windows. Also refactor the managed keys into a data-driven table so the schema gate, value-equality guard, dispatch, and hydration-retry are shared - adding a new managed key is now a single entry with no copy/pasted logic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes an infinite cross-window update loop where AgentHostTerminalContribution re-pushed agent-host root-config values on every rootState change, causing multiple VS Code windows with differing terminal settings to “fight” via repeated root/configChanged dispatches. The new approach only re-pushes when a managed key’s schema first appears (hydration), and consolidates the managed-key logic into a single data-driven pipeline.
Changes:
- Refactors managed agent-host root-config pushes into a declarative
_managedKeystable and a shared async_push(entry)pipeline. - Changes the
rootState.onDidChangebehavior to re-push only on schema appearance (hydration), ignoring value-only changes to prevent cross-window ping-pong. - Updates/extends tests with
flush()usage and adds regressions ensuring value-only root-state updates don’t trigger re-dispatch.
Show a summary per file
| File | Description |
|---|---|
| src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostTerminalContribution.ts | Implements schema-seen gating to prevent cross-window config wars; refactors pushes into a shared managed-key pipeline. |
| src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostTerminalContribution.test.ts | Adds regression coverage for value-only root-state changes and adjusts timing with await flush() for async push behavior. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 1
Flip the agent host root-config key polarity so the custom terminal tool is disabled by default in a natural way. `enableCustomTerminalTool` defaults to false, and since `getRootValue` returns undefined when unset and consumers compare against `true`, "unset -> disabled" now falls out naturally without touching getRootValue. The workbench-side push also drops its negation and maps 1:1 to the `chat.agentHost.customTerminalTool.enabled` setting. Note: this is a host-side schema key (not part of the versioned wire protocol), so no protocol bump. A stale `disableCustomTerminalTool` value in a persisted agent-host-config.json is simply ignored; the workbench re-pushes the new key on connect. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses review feedback: after awaiting computeValue(), re-check _schemaHasKey() rather than just rootState.config existence, so a host restart / schema refresh that retracts the key can't defeat the schema gate for older / 3rd-party hosts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
vijayupadya
approved these changes
Jun 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix #320011
Problem
The local agent host's root config is shared across all VS Code windows.
AgentHostTerminalContributionpushes a couple of host-managed values intodefaultShellandand listened torootState.onDidChangeto re-push them.disableCustomTerminalToolitThe bug: that listener re-pushed on every root-state change, including value changes made by other windows. So two windows with different terminal settings would ping-pong
root/configChangedactions forever, each forcing its own value back over the other's.Captured agent-host protocol logs show ~8000
root/configChangeddispatches in 25s, alternating between two windows:disableCustomTerminalTooldefaultShell79ed98fdfalsepwsh.exe)d551eb09trueFix
The
rootState.onDidChangere-push only ever needed to do one thing: retry the initial push once the host's root-config schema hydrates (the first push from_reconcile()can race an undefinedrootState.value). It was never meant to react to value changes.present**, tracked via a
Set<AgentHostConfigKey>. Value-only changes (another window writing a different value) are ignored, which breaks the loop. The existing value-equality guard still prevents redundant self-dispatches.Refactor
While here, the two managed keys are now a data-driven table (
IManagedRootConfigKey[]). The schema gate, value-equality guard, dispatch, and hydration-retry are shared in a single generic_push(entry). Adding a new managed root-config key is now one table no new fields, no copy/pasted push logic.entryNotes for reviewers
re-dispatches disableCustomTerminalTool when the enabled setting changes) gained anawait flush()because the push pipeline is now uniformly async (computeValuemay be async). The dispatch simply lands a microtask behavior is otherwise unchanged.laterType-check (
tsgo), ESLint, and all 15 tests pass.(Written by Copilot)