Await session state before computing the customization migration hint - #334958
Conversation
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>
There was a problem hiding this comment.
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
New issues introduced by this change (2)
| Severity | Finding |
|---|---|
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… |
|
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>
|
Good catch — the per-call deadline was applied once per source-folder type, not once per hint.
Fixed by memoizing the wait on the session-state subscription entry (
Added 34 passing across the three affected suites; |


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 fromIAgentHostCustomizationService.getCustomizations(), which reads an AHP subscription synchronously:IAgentSubscription.valueis documented asundefineduntil the first snapshot arrives, and_ensureSessionStateSubscriptioncreates the subscription on demand — so the first read after creation always yieldsundefined, 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)toIAgentHostCustomizationServiceand await it inprovideSourceFolders(), so one-shot callers can distinguish "not loaded yet" from "no customizations".WorkbenchAgentHostCustomizationServicewaits for the subscription's first snapshot (anErroralso counts as settled).getCustomizations()stays synchronous — reactive UI should keep reading the current value and re-rendering ononDidChangeCustomizations.The wait is bounded (2s) and cancellable: the chat request path awaits the hint in a blocking
Promise.allbefore 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 doesawait scope.whenResolved()for the MCP half of the same service.Validation
agentHostCustomizationService.test.ts,customizationMigrationServiceImpl.test.ts,remoteAgentHostCustomizationHarness.test.ts: 26 passing.npm run typecheck-clientclean.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.