Skip to content

Conditional agent-window auth for signed-out users - #328990

Merged
TylerLeonhardt merged 2 commits into
mainfrom
wip/discovered-config-onboarding
Aug 4, 2026
Merged

TylerLeonhardt merged 2 commits into
mainfrom
wip/discovered-config-onboarding

Conversation

@TylerLeonhardt

Copy link
Copy Markdown
Member

Note

Draft. Behind an experimentation setting that is off by default — with chat.agentHost.allowSignedOutWhenUsable unset, 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, APPLICATION scope).

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:

'none' | 'github' | 'unusable'

The third state is load-bearing rather than decorative. Claude pinned to native by an explicit claudeUseCopilotProxy: false with no credentials still advertises the Copilot resource as required: 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 empty HOME), 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 into ISessionType and core sessions code reads only the provider-agnostic model.

Known gap

observeUsableWithoutGitHub does not reuse getSessionTypeAvailability, 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 over chatEntitlementService.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 a TODO at the call site.

Testing

  • Truth tables for the notification predicate and the authRequirement resolution, including the native WITHOUT credentials → unusable row
  • claudeTransportMode precedence matrix + credential detection
  • 105 tests passing across the affected suites; typecheck-client, valid-layers-check, full compile clean
  • Verified end-to-end in the Agents window signed out (--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 off

Not included

A CONTEXT.md glossary and an ADR were written alongside this and are intentionally left out of this PR.

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>
Copilot AI balanced review requested due to automatic review settings August 4, 2026 16:05

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.

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 (using onDidDismiss) 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

Comment thread src/vs/sessions/browser/sessionsSetUpService.ts
Comment thread src/vs/sessions/browser/sessionsAuthGate.ts Outdated
Comment thread src/vs/sessions/contrib/chat/browser/newChatWidget.ts Outdated
- 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
TylerLeonhardt marked this pull request as ready for review August 4, 2026 17:14
@vs-code-engineering

Copy link
Copy Markdown
Contributor

📬 CODENOTIFY

The following users are being notified based on files changed in this PR:

Benjamin Christopher Simmonds (@benibenj)

Matched files:

  • src/vs/sessions/browser/parts/mobile/mobileTitlebarPart.ts

Sandeep Somavarapu (@sandy081)

Matched files:

  • src/vs/sessions/services/sessions/browser/sessionsManagementService.ts
  • src/vs/sessions/services/sessions/common/session.ts
  • src/vs/sessions/services/sessions/common/sessionsManagement.ts
  • src/vs/sessions/services/sessions/test/browser/sessionNavigation.test.ts
  • src/vs/sessions/services/sessions/test/browser/sessionsManagementService.test.ts

Ladislau Szomoru (@lszomoru)

Matched files:

  • src/vs/sessions/services/sessions/browser/sessionsManagementService.ts
  • src/vs/sessions/services/sessions/common/session.ts
  • src/vs/sessions/services/sessions/common/sessionsManagement.ts
  • src/vs/sessions/services/sessions/test/browser/sessionNavigation.test.ts
  • src/vs/sessions/services/sessions/test/browser/sessionsManagementService.test.ts

@TylerLeonhardt
TylerLeonhardt merged commit 59bca39 into main Aug 4, 2026
46 of 47 checks passed
@TylerLeonhardt
TylerLeonhardt deleted the wip/discovered-config-onboarding branch August 4, 2026 18:09
@vs-code-engineering vs-code-engineering Bot added this to the 1.133.0 milestone 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>
@vs-code-engineering vs-code-engineering Bot locked and limited conversation to collaborators Sep 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants