Repository navigation
Conditional agent-window auth for signed-out users - #328990
Merged
Merged
Conversation
Behind the experimentation setting `chat.agentHost.allowSignedOutWhenUsable` (default off), the Agents window no longer unconditionally forces GitHub sign-in. When the user is signed out and some registered session type can run on its own credentials — Claude in native mode with an existing local setup — the window opens and a calm chat-input notification explains what happened, offering sign-in for anyone who meant to use a Copilot subscription. Whether a type requires GitHub is derived from the agent's advertised protected resources rather than a static flag, and surfaces on the provider-agnostic `ISessionType.authRequirement` as `none | github | unusable`. The third state is load-bearing: a Claude pinned to native by an explicit `claudeUseCopilotProxy: false` with no credentials advertises the Copilot resource as `required: false`, so a boolean would report it usable. Its model catalog is what distinguishes it, which is why Claude now publishes an empty catalog in that case instead of the SDK's static `supportedModels()` list. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds opt-in signed-out access to the Agents window when a local agent can operate without GitHub authentication.
Changes:
- Derives session authentication requirements from protected resources and model availability.
- Adds reactive auth gating, account presentation, and discovered-configuration notification.
- Detects Claude native credentials and dynamically selects native or Copilot transport.
Show a summary per file
| File | Description |
|---|---|
src/vs/workbench/contrib/chat/test/common/mockChatSessionsService.ts |
Supports dynamic sign-in predicates. |
src/vs/workbench/contrib/chat/test/browser/chatSessions/chatSessionsService.test.ts |
Tests dynamic authentication requirements. |
src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostProtectedResourcesService.test.ts |
Tests protected-resource updates. |
src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostChatContribution.test.ts |
Stubs the new resource service. |
src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostAllowSignedOutWhenUsableContribution.test.ts |
Tests configuration forwarding. |
src/vs/workbench/contrib/chat/electron-browser/chat.contribution.ts |
Registers configuration forwarding. |
src/vs/workbench/contrib/chat/common/chatSessionsService.ts |
Extends session authentication metadata. |
src/vs/workbench/contrib/chat/browser/chatSessions/chatSessions.contribution.ts |
Evaluates and observes dynamic requirements. |
src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts |
Registers the experimental setting. |
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostProtectedResourcesService.ts |
Tracks provider protected resources. |
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostChatContribution.ts |
Derives agent-host sign-in requirements. |
src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostAllowSignedOutWhenUsableContribution.ts |
Forwards the opt-in to the host. |
src/vs/sessions/test/browser/sessionsAuthGate.test.ts |
Tests notification gating. |
src/vs/sessions/services/sessions/test/browser/sessionsManagementService.test.ts |
Updates session-type fixtures. |
src/vs/sessions/services/sessions/common/session.ts |
Defines authentication requirement states. |
src/vs/sessions/contrib/providers/localChatSessions/browser/localChatSessionsProvider.ts |
Marks local chat as GitHub-backed. |
src/vs/sessions/contrib/providers/copilotChatSessions/browser/copilotChatSessionsProvider.ts |
Marks Copilot providers as GitHub-backed. |
src/vs/sessions/contrib/providers/agentHost/test/browser/sessionTypeAuthRequirement.test.ts |
Tests agent requirement resolution. |
src/vs/sessions/contrib/providers/agentHost/browser/localAgentHost.contribution.ts |
Registers signed-out contributions. |
src/vs/sessions/contrib/providers/agentHost/browser/baseAgentHostSessionsProvider.ts |
Publishes reactive authentication states. |
src/vs/sessions/contrib/providers/agentHost/browser/agentHostDiscoveredConfigNotification.ts |
Adds the signed-out configuration nudge. |
src/vs/sessions/contrib/chat/test/browser/sessionTypePicker.test.ts |
Updates picker fixtures. |
src/vs/sessions/contrib/chat/browser/newChatWidget.ts |
Prefers signed-out-usable session types. |
src/vs/sessions/contrib/automations/test/browser/automationDialog.test.ts |
Updates automation fixtures. |
src/vs/sessions/contrib/accountMenu/test/browser/accountTitleBarState.test.ts |
Tests optional sign-in presentation. |
src/vs/sessions/contrib/accountMenu/browser/account.contribution.ts |
Reacts to signed-out usability. |
src/vs/sessions/browser/sessionsSetUpService.ts |
Conditionally bypasses mandatory sign-in. |
src/vs/sessions/browser/sessionsAuthGate.ts |
Implements the shared auth predicate. |
src/vs/sessions/browser/parts/mobile/mobileTitlebarPart.ts |
Keeps mobile behavior unchanged. |
src/vs/sessions/browser/accountTitleBarState.ts |
Adds calm optional sign-in state. |
src/vs/platform/agentHost/test/node/claudeTransportMode.test.ts |
Tests transport and credential detection. |
src/vs/platform/agentHost/test/node/claudeAgent.test.ts |
Tests native/proxy transitions. |
src/vs/platform/agentHost/test/common/agentService.test.ts |
Tests protected-resource interpretation. |
src/vs/platform/agentHost/node/claude/claudeTransportMode.ts |
Resolves Claude transport mode. |
src/vs/platform/agentHost/node/claude/claudeAgent.ts |
Applies dynamic transport and model behavior. |
src/vs/platform/agentHost/common/agentService.ts |
Adds setting and resource helpers. |
src/vs/platform/agentHost/common/agentHostCustomizationConfig.ts |
Adds the host configuration key. |
Review details
Suppressed comments (2)
src/vs/sessions/contrib/providers/agentHost/browser/agentHostDiscoveredConfigNotification.ts:101
- Deleting the notification also clears
ChatInputNotificationService's dismissed-id state. After the user clicks X, any false→true condition transition (for example toggling the opt-in off and back on, or signing in and out) executes this delete, resets_shown, and re-pushes the banner, so X is not a dismissal for the window as promised. Preserve a contribution-level window dismissal flag (usingonDidDismiss) across condition changes.
if (!show) {
if (this._shown) {
this._chatInputNotificationService.deleteNotification(DISCOVERED_CONFIG_NOTIFICATION_ID);
this._shown = false;
}
src/vs/sessions/contrib/providers/agentHost/browser/agentHostDiscoveredConfigNotification.ts:88
- The window gate accepts any session type with
authRequirement === None, including Codex using OpenAI credentials, but this notification is hard-coded to Claude. In that supported path the modal is skipped without the explanatory notification promised by the PR. Derive the nudge from whichever type opened the gate, or constrain the gate to the same supported type.
private _update(): void {
// The Claude agent-host session type, once the host has advertised it.
const claude = this._sessionsManagementService.getAllSessionTypes()
.find(type => (type.chatSessionType ?? type.id) === SessionType.AgentHostClaude);
- Files reviewed: 37/37 changed files
- Comments generated: 4
- Review effort level: Balanced
- The window gate's inputs resolve asynchronously (the agent host advertises Claude at `AfterRestored`), but the only subscription was installed after setup completed. A signed-out startup could therefore show the non-dismissible sign-in modal and never reconsider. The subscription is now lifetime-scoped and retires an open modal when the answer flips to usable. - `getAllSessionTypes()` deduplicates by id (first provider wins), so a usable type from a second provider was invisible to the gate. Added `getAllProviderSessionTypes()` and used it for the gate and the notification lookup; the signed-out session-type fallback now matches on provider too. - "Don't Show Again" used `StorageTarget.USER`, which settings sync carries across machines, contradicting the intended machine-local scope. Now `StorageTarget.MACHINE`. Also fixes two claudeAgent tests that asserted native enumeration without a credential present, and adds coverage for the empty-catalog case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TylerLeonhardt
marked this pull request as ready for review
August 4, 2026 17:14
Contributor
📬 CODENOTIFYThe following users are being notified based on files changed in this PR: Benjamin Christopher Simmonds (@benibenj)Matched files:
Sandeep Somavarapu (@sandy081)Matched files:
Ladislau Szomoru (@lszomoru)Matched files:
|
Vritant Bhardwaj (vritant24)
approved these changes
Aug 4, 2026
Don Jayamanne (DonJayamanne)
added a commit
that referenced
this pull request
Aug 4, 2026
* origin/main: (31 commits) Improve workspace picker preselection (#328995) agentHost: support Codex custom agents and runtime enablement (#328956) Finalizes customEditorPriority proposal. Closes #292379 (#329002) Add Agents window startup A/A experiment trigger (#328454) sessions: show created session pill in response summary (#328984) Fix onboarding microphone picker visibility (#329011) Explains how to develop the markdown editor (#329009) Conditional agent-window auth for signed-out users (#328990) Fix BYOK enterprise policy handling in agent host Agent Host changes for fix/agent-host-byok-enterprise-policy agentHost: drive tool execution from the session input queue (#328989) Make Integrated Browser smoke tests deterministic across build qualities (#328983) Accept box sizing screenshot changes Avoid large Component Fixtures step outputs Remove component fixture box sizing reset fix: guard stale line numbers in test decorations (fixes #328988) sessions: fix maximized side pane toggle (#328974) Add component fixture rendering controls Reduce floating panel margins for layout consistency (#328963) agentHost: support file completions across workspace roots (#328944) ... # Conflicts: # src/vs/sessions/SESSIONS.md # src/vs/sessions/contrib/providers/agentHost/browser/baseAgentHostSessionsProvider.ts
TylerLeonhardt
added a commit
that referenced
this pull request
Aug 5, 2026
The conditional-auth UI added in #328990 conflated "auth not resolved yet" with "signed out". IDefaultAccountService.currentDefaultAccount is a synchronous getter that returns null for everyone until the first async resolution completes — and that initial resolution fires no change event. During the startup gap a signed-in user therefore reads as signed-out, so the sign-in modal and the discovered-config nudge flash. Worse, once auth finally resolves nothing retires the already-raised modal, so it stays up. Gate both reactive consumers (sessionsSetUpService and the discovered config notification) on a resolved-auth signal, learned via a one-shot getDefaultAccount() await — the only reliable indicator that resolution happened, since the initial null->account assignment is event-silent. While unresolved, neither consumer acts, so gap-time Claude native<->proxy churn is inert and the normal sign-in watch owns the signed-in path. Consolidate the shared unresolved-vs-signed-out logic both consumers were duplicating into a unit-tested conditionalAuthState helper. The intended signed-out conditional-auth behaviour from #328990 is preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
TylerLeonhardt
added a commit
that referenced
this pull request
Aug 5, 2026
…rtup (#329269) * sessions: don't flash sign-in modal at signed-in users during startup The conditional-auth UI added in #328990 conflated "auth not resolved yet" with "signed out". IDefaultAccountService.currentDefaultAccount is a synchronous getter that returns null for everyone until the first async resolution completes — and that initial resolution fires no change event. During the startup gap a signed-in user therefore reads as signed-out, so the sign-in modal and the discovered-config nudge flash. Worse, once auth finally resolves nothing retires the already-raised modal, so it stays up. Gate both reactive consumers (sessionsSetUpService and the discovered config notification) on a resolved-auth signal, learned via a one-shot getDefaultAccount() await — the only reliable indicator that resolution happened, since the initial null->account assignment is event-silent. While unresolved, neither consumer acts, so gap-time Claude native<->proxy churn is inert and the normal sign-in watch owns the signed-in path. Consolidate the shared unresolved-vs-signed-out logic both consumers were duplicating into a unit-tested conditionalAuthState helper. The intended signed-out conditional-auth behaviour from #328990 is preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * sessions: replay usability state once the account resolves Addresses review feedback: gating _onUsableWithoutGitHubChanged on _accountResolved dropped any usability transition that landed during the unresolved window without replaying it. The native paths re-read the current usability after they await the account (via _showWelcome -> _mustForceGitHubSignIn), but the web path (_checkWebAuth) has no such post-resolution re-check, so a genuinely signed-out, opted-in user whose agent became usable during the gap would stay stranded on the sign-in dialog that nothing else retires. When the account resolves, replay the current usability state — scoped to usable === true, the only transition that was wrongly dropped; a not-usable state is still owned by the initial setup flow. Signed-in users remain a no-op (the handler early-returns), so the original fix is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sascha Zepter (saschazepter)
pushed a commit
to saschazepter/vscode
that referenced
this pull request
Aug 6, 2026
The conditional-auth UI added in microsoft#328990 conflated "auth not resolved yet" with "signed out". IDefaultAccountService.currentDefaultAccount is a synchronous getter that returns null for everyone until the first async resolution completes — and that initial resolution fires no change event. During the startup gap a signed-in user therefore reads as signed-out, so the sign-in modal and the discovered-config nudge flash. Worse, once auth finally resolves nothing retires the already-raised modal, so it stays up. Gate both reactive consumers (sessionsSetUpService and the discovered config notification) on a resolved-auth signal, learned via a one-shot getDefaultAccount() await — the only reliable indicator that resolution happened, since the initial null->account assignment is event-silent. While unresolved, neither consumer acts, so gap-time Claude native<->proxy churn is inert and the normal sign-in watch owns the signed-in path. Consolidate the shared unresolved-vs-signed-out logic both consumers were duplicating into a unit-tested conditionalAuthState helper. The intended signed-out conditional-auth behaviour from microsoft#328990 is preserved. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Aug 7, 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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Note
Draft. Behind an experimentation setting that is off by default — with
chat.agentHost.allowSignedOutWhenUsableunset, behaviour is unchanged.What
The Agents window currently forces GitHub sign-in with a non-dismissible modal whenever the user is signed out. With this opt-in enabled, it no longer does so when some registered session type can run on the user's own credentials — today that means Claude in native mode with an existing local setup.
Instead of the modal, the window opens and a chat-input notification explains what happened, with a Sign in to GitHub action for anyone who meant to use a Copilot subscription, an X (dismiss for this window), and a bell-slash Don't Show Again (machine-wide,
APPLICATIONscope).How
Whether a type requires GitHub is derived from the agent's advertised protected resources, not a static flag — that signal already crosses the agent-host IPC boundary and updates reactively. It surfaces on the provider-agnostic
ISessionType.authRequirement:The third state is load-bearing rather than decorative. Claude pinned to native by an explicit
claudeUseCopilotProxy: falsewith no credentials still advertises the Copilot resource asrequired: false, so a boolean would report it usable-without-GitHub — opening the window onto an agent that fails on its first turn, and claiming a configuration was discovered that does not exist.What distinguishes that case is the model catalog, so Claude now publishes an empty catalog when native without a credential. The SDK's
supportedModels()is a static list that answers even with no credentials (verified: resolves with 4 models under a scrubbed env and emptyHOME), so publishing it would advertise models that fail on use. This also corrects behaviour that predates this feature, since the explicit override reaches native regardless of the flag.Layering: the sessions core cannot import
vs/workbench/contrib, so providers resolve the per-type fact intoISessionTypeand core sessions code reads only the provider-agnostic model.Known gap
observeUsableWithoutGitHubdoes not reusegetSessionTypeAvailability, which the per-type pickers use for a related question. That helper is unreachable from this layer, and its model check cannot detect a credential-less native agent. The cost is two predicates that can drift — they already differ overchatEntitlementService.anonymous, which the pickers honour and this gate ignores. Pre-existing (with the opt-in off the gate collapses to today's behaviour) but worth converging; there is aTODOat the call site.Testing
authRequirementresolution, including thenative WITHOUT credentials → unusablerowclaudeTransportModeprecedence matrix + credential detectiontypecheck-client,valid-layers-check, full compile clean--use-mock-keychain): notification renders, X dismisses for the window only and returns on reload, Don't Show Again persists across reload, and the notification tears down reactively when the opt-in is toggled offNot included
A
CONTEXT.mdglossary and an ADR were written alongside this and are intentionally left out of this PR.