Skip to content

fix: guard against missing default chat agent in sendRequest (fixes #321961)#321967

Open
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/chat-default-agent-undefined-321961-f02000df3f9b59f2
Open

fix: guard against missing default chat agent in sendRequest (fixes #321961)#321967
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/chat-default-agent-undefined-321961-f02000df3f9b59f2

Conversation

@vs-code-engineering

Copy link
Copy Markdown
Contributor

Summary

ChatService.sendRequest reads the default chat agent with a non-null assertion — getDefaultAgent(location, options?.modeInfo?.kind)! — but that method is declared to return IChatAgent | undefined and genuinely returns undefined when no activated agent matches the requested location/mode. The undefined value then flows into the _onDidSubmitAgent event payload (whose agent field is typed non-nullable), and the chatInputEditorContrib listener crashes reading agent.id in agentAndCommandToKey. This is a high-volume crash (298 hits / 149 users on 1.125.0).

Fixes #321961
Recommended reviewer: @roblourens

Culprit Commit

Field Value
Commit Not confidently identified
Author
PR
Message
Why The non-null assertion (getDefaultAgent(...)!) is a latent type-system bypass in chatServiceImpl.ts. The telemetry bucket has hits only for the shipped commit 93cfdd48 (1.125.0, FirstSeen 2026-06-17), so the history API yields no clean last-good/first-bad range, and the checkout is a depth-1 shallow clone, so git blame/git log cannot attribute the line. The companion sibling code path (processNextPendingRequest) already guards this exact case, indicating the sendRequest site simply missed the guard. The recent spike most plausibly correlates with mode-aware filtering in getDefaultAgent (it rejects agents whose modes do not include the request mode), which can make the default-agent lookup return undefined for a reachable mode — but no single triggering commit could be confirmed from the available data.

Code Flow

sequenceDiagram
    participant User as User
    participant Service as ChatService.sendRequest
    participant Widget as ChatWidget
    participant Contrib as chatInputEditorContrib

    User->>Service: submit chat input
    Note over Service: ⚠️ Root cause:<br/>getDefaultAgent(location, mode)!<br/>returns undefined, asserted non-null
    Service->>Widget: sent result with data.agent = undefined
    Widget->>Contrib: _onDidSubmitAgent.fire({ agent: undefined })
    Note over Contrib: 💥 TypeError:<br/>Cannot read properties of<br/>undefined (reading 'id')
Loading

Affected Files

File Role
src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts Producer / fix sitesendRequest uses getDefaultAgent(...)! (line 1018), producing the undefined agent.
src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts Intermediate — re-fires the value via _onDidSubmitAgent.fire({ agent: sent.data.agent }) (emitter payload typed non-nullable).
src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputEditorContrib.ts Crash site (consumer)onDidSubmitAgent listener calls agentAndCommandToKey(e.agent, ...), which reads agent.id.
src/vs/workbench/contrib/chat/common/chatService/chatService.ts Type contract — IChatSendRequestData.agent: IChatAgentData (non-nullable), the contract that the bypass violates.

Repro Steps

  1. Open a chat surface and submit input in a state where no activated default agent matches the request's location/mode — e.g. early in a window's lifetime before the default agent is registered/activated for that location, or in a mode whose eligible agent set is empty after mode-aware filtering.
  2. getDefaultAgent(location, mode) returns undefined. With no explicit @agent part in the prompt and no silent agent, agent resolves to undefined.
  3. The request is accepted and onDidSubmitAgent fires with agent: undefined.
  4. agentAndCommandToKey reads agent.idTypeError: Cannot read properties of undefined (reading 'id').

How the Fix Works

Chosen approach (src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts): Remove the non-null assertion on the getDefaultAgent(location, options?.modeInfo?.kind) call (the type-system bypass at chatServiceImpl.ts:1018) and add a guard clause that bails out early when no default agent is available:

const defaultAgent = this.chatAgentService.getDefaultAgent(location, options?.modeInfo?.kind);
if (!defaultAgent) {
    this.logService.warn('sendRequest', `No default agent for location ${location}`);
    return { kind: 'rejected', reason: 'No default agent available' };
}

This fixes the data producer rather than guarding the crash site: the bypass is where the IChatAgent | undefined return is wrongly narrowed to non-null, so the guard belongs there. After the guard, defaultAgent is IChatAgent, so the downstream agent value and the _sendRequestAsync argument can never be undefined, and the non-nullable _onDidSubmitAgent payload contract is honored — the crash-site listener can never receive an undefined agent. The fix mirrors the existing guard in the sibling method processNextPendingRequest in this same file, which already handles the identical "no default agent" case with a rejected result and a logService.warn. The violation is surfaced (warning + rejected result), not silently swallowed, so error telemetry is preserved.

Alternatives considered:

  • Null-check inside agentAndCommandToKey or the onDidSubmitAgent listener (the crash site) — rejected because it coerces/suppresses at the consumer and hides the producer bug; the event payload is declared non-nullable, so the violating value must be stopped upstream.
  • Coercing the emitter payload to a fallback agent in chatWidget.ts — rejected for the same reason: it masks an invalid request state instead of rejecting it where the request is assembled.

Recommended Owner

@roblourens — In triage/working-areas.md he owns "ChatModel creation, management, persistence" and "Chat participant API", which map to chatServiceImpl.ts (the producer/fix site). He previously fixed the analogous "don't throw when the agent is gone" crash in this same file (PR #313068). Team-membership gate: passes (core maintainer with write access, listed in working-areas.md). Liveness gate: passes (multiple commits on 2026-06-10, well within 90 days).

Generated by errors-fix · 2K AIC · ⌖ 210.8 AIC · ⊞ 69.3K ·

…321961)

ChatService.sendRequest used a non-null assertion on getDefaultAgent(location, mode), which returns IChatAgent | undefined. When no activated agent matches the location/mode, defaultAgent became undefined and flowed into the _onDidSubmitAgent event payload (typed non-nullable), crashing the chatInputEditorContrib listener at agentAndCommandToKey when reading agent.id.

Mirror the existing guard in processNextPendingRequest: bail out with a rejected result and a logService.warn instead of asserting non-null.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 18, 2026 16:56

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 encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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 encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@vs-code-engineering vs-code-engineering Bot marked this pull request as ready for review June 18, 2026 16:58
@vs-code-engineering vs-code-engineering Bot enabled auto-merge (squash) June 18, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Error] unhandlederror-Cannot read properties of undefined (reading 'id')

2 participants