Skip to content

Await session state before computing the customization migration hint - #334958

Merged
Martin Aeschlimann (aeschli) merged 4 commits into
mainfrom
fix/customization-migration-hint-readiness
Sep 7, 2026
Merged

Await session state before computing the customization migration hint#334958
Martin Aeschlimann (aeschli) merged 4 commits into
mainfrom
fix/customization-migration-hint-readiness

Conversation

@aeschli

@aeschli Martin Aeschlimann (aeschli) commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #334953
Fixes #334553

Problem

The customization migration hint is missing on the first request of a new chat, for every Agent Host harness (verified with Copilot, Claude and Codex). Sending a second message in the same chat immediately shows the correct hint, and the Customizations overview shows the correct counts even before any request.

Cause

Both the hint and the overview use the same ICustomizationMigrationService.computeMigration(...), so this is not a difference in candidate discovery — it is a race on when the shared computation reads session state.

provideSourceFolders() resolves migration destinations from IAgentHostCustomizationService.getCustomizations(), which reads an AHP subscription synchronously:

customizations: sessionState?.customizations ?? [],

IAgentSubscription.value is documented as undefined until the first snapshot arrives, and _ensureSessionStateSubscription creates the subscription on demand — so the first read after creation always yields undefined, which the ?? [] collapses into "no customizations".

Subscriptions are keyed by the chat UI resource. On first send, _materializeUntitledSession() swaps the untitled resource (agent-host-PROVIDER:/untitled-<uuid>) for a freshly minted real resource. The hint then runs against that brand-new resource, so a subscription is created at that instant, has no snapshot, and every migration candidate is filtered out — silently, indistinguishable from "nothing to migrate".

Anything reading later (the reactive overview, the second request) sees the arrived snapshot, which is why only the first request is affected. It also explains why the symptom looks intermittent: when the subscription happens to be warm, the hint appears.

Fix

Add whenCustomizationsReady(sessionResource, token) to IAgentHostCustomizationService and await it in provideSourceFolders(), so one-shot callers can distinguish "not loaded yet" from "no customizations".

  • WorkbenchAgentHostCustomizationService waits for the subscription's first snapshot (an Error also counts as settled).
  • The base and null implementations resolve immediately; their targets are backed by already-materialized provider state.
  • getCustomizations() stays synchronous — reactive UI should keep reading the current value and re-rendering on onDidChangeCustomizations.

The wait is bounded (2s) and cancellable: the chat request path awaits the hint in a blocking Promise.all before sending the user's message, so an unbounded wait would risk delaying the first message. On timeout the caller falls back to today's behaviour.

This mirrors the existing readiness pattern in this area — computeMcpServerMigration() already does await scope.whenResolved() for the MCP half of the same service.

Validation

  • New unit tests cover the deferred-then-resolved path, the already-loaded path, and the subscription-failure path.
  • agentHostCustomizationService.test.ts, customizationMigrationServiceImpl.test.ts, remoteAgentHostCustomizationHarness.test.ts: 26 passing.
  • npm run typecheck-client clean.
  • Verified live in a dev build: a fresh Copilot chat now shows Found 1 user customization file that is present but not used by Copilot and could be migrated. on the first message.

Note: #334938 / #334942 is a separate Claude-specific gap (missing destinations on every request). That fix is still needed; this one addresses the first-request timing for all harnesses.

The customization migration hint reads migration destinations through
AgentCustomizationItemProvider.provideSourceFolders, which resolves them
from a session-state subscription. IAgentSubscription.value is undefined
until its first snapshot arrives, and the workbench service collapses
that to an empty customization list.

Session-state subscriptions are keyed by chat UI resource. On the first
send of a new chat the untitled resource is materialized into a fresh
real resource, so a new subscription is created at that moment and its
first read yields no customizations. Every migration candidate is then
filtered out and no hint is shown, even though the same computation
reports the correct counts once the snapshot lands.

Add whenCustomizationsReady so one-shot callers can distinguish 'not
loaded yet' from 'no customizations', and await it in provideSourceFolders.
The wait is bounded and cancellable because the chat request path blocks
on the hint before sending the user's message.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings September 7, 2026 18:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Repeated per-type waits can delay the first request for roughly six seconds rather than the intended two-second maximum.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 Medium severity · 1 Low severity

New issues introduced by this change (2)
Severity Finding
Medium severity src/​vs/​workbench/​contrib/​chat/​browser/​agentSessions/​agentHost/​agentCustomizationItemProvider.ts — The 2-second bound is applied once per source-folder type, not once per hint. createFileMigration
Low severity src/​vs/​workbench/​contrib/​chat/​browser/​agentSessions/​agentHost/​agentHostCustomizationService.ts — This contract says resolution guarantees a real snapshot, but the implementation also resolves on…
What changed in this PR

Fixes first-request customization migration hints by awaiting Agent Host session-state hydration.

Changes:

  • Adds bounded, cancellable customization readiness.
  • Awaits readiness before resolving migration folders.
  • Adds readiness tests and updates a remote harness mock.
File Description
agentHostCustomizationService.test.ts Tests snapshot and failure readiness paths.
agentHostCustomizationService.ts Adds the readiness API and implementation.
agentCustomizationItemProvider.ts Awaits readiness before reading customizations.
remoteAgentHostCustomizationHarness.test.ts Implements the new API in the test mock.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
createFileMigration queries provideSourceFolders once per target prompt
type, sequentially, and computeMigrationHint runs categories covering
agent, instructions and skill candidates. With a per-call deadline a
subscription that never hydrates could therefore block the first message
for roughly three deadlines instead of one.

Memoize the wait on the session-state subscription entry so every
source-folder query behind a hint joins the same bounded wait. A new
subscription generation - including the untitled to real rebind that
backs a first send - starts with no memo, so that case still gets a full
wait. Each caller races the shared wait against its own token so one
cancellation cannot settle it for the others.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@aeschli

Copy link
Copy Markdown
Contributor Author

Good catch — the per-call deadline was applied once per source-folder type, not once per hint.

createFileMigration queries provideSourceFolders once per target prompt type in a sequential loop, and the ConfiguredLocations category spans agent + instructions + skill, so a subscription that never hydrated could block the first message for roughly three deadlines (~6s) rather than one.

Fixed by memoizing the wait on the session-state subscription entry (ISessionStateSubscriptionEntry.readiness), so every source-folder query behind a hint — and the categories that run in parallel — join the same bounded wait. Two details worth calling out:

  • A new subscription generation starts with no memo, and _ensureSessionStateSubscription replaces the entry when the backend session changes. The untitled → real rebind that causes this bug therefore still gets a full wait.
  • Each caller races the shared wait against its own token, so one caller's cancellation cannot settle it for the others.

Added whenCustomizationsReady shares one bounded wait across every prompt-type query as the multi-type regression test. It asserts the listener count stops growing across repeated queries rather than using a wall-clock bound, which would be flaky on CI. Verified it genuinely catches the regression: with the memo removed it reports 4 instead of 2.

34 passing across the three affected suites; typecheck-client clean.

@aeschli
Martin Aeschlimann (aeschli) merged commit 405c678 into main Sep 7, 2026
33 checks passed
@aeschli
Martin Aeschlimann (aeschli) deleted the fix/customization-migration-hint-readiness branch September 7, 2026 20:02
@vs-code-engineering vs-code-engineering Bot added this to the 1.138.0 milestone Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants