Skip to content

fix: tolerate unregistered LanguageModelIgnoredFileProvider handle in $isFileIgnored (fixes #323156)#323163

Open
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/lm-ignored-file-provider-race-323156-3d2d1ae0f7279535
Open

fix: tolerate unregistered LanguageModelIgnoredFileProvider handle in $isFileIgnored (fixes #323156)#323163
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/lm-ignored-file-provider-race-323156-3d2d1ae0f7279535

Conversation

@vs-code-engineering

Copy link
Copy Markdown
Contributor

Summary

ExtHostLanguageModels.$isFileIgnored throws Unknown LanguageModelIgnoredFileProvider when it receives an RPC for a provider handle that is no longer in its local _ignoredFileProviders map. This is a benign cross-process use-after-dispose / stale-handle race across the main-thread → extension-host RPC boundary: the extHost deletes its local provider entry synchronously on disposal while the main-thread registration is briefly still live (the $unregisterFileIgnoreProvider RPC is async), or while an $isFileIgnored call is already in flight the other way. The throw surfaces as an unhandled error in extHost telemetry.

The issue's stable-anomaly comment reports this as a pre-existing bucket that spiked 60.5× at the 1.125.1 → 1.126.0 boundary (121 users / 230 hits), with the signal "a missing guard when the provider handle is gone" — i.e. the frequency rose but the underlying defect is old.

The fix adds the missing guard: return false (not ignored) for an unknown handle instead of throwing, matching the default the main-thread LanguageModelIgnoredFilesService.fileIsIgnored already returns when no provider is registered.

Fixes #323156
Recommended reviewer: @roblourens

Culprit Commit

The throw is pre-existing, introduced together with the feature in bcf6a6af1c18a27b00069f7713d8bc063bcadc33 — "Add LanguageModelIgnoredFileProvider (#231696)" by @roblourens (2024-10-18). Confirmed against that PR's source: the original $isFileIgnored already did if (!provider) { throw new Error('Unknown LanguageModelIgnoredFileProvider'); }.

This is therefore a latent race, not a fresh code regression. Telemetry onset is ~mid-2026 (≈1.119 insiders), and the stable-release anomaly scan reports a 60.5× volume spike across the full 1.125.1 → 1.126.0 stable range (fcf6047...7e7950d). Per that scan, onset narrowing within a stable-to-stable range is N/A for a volume spike of an existing bucket, so no single amplifying commit is pinpointed — the spike reflects more frequent provider (de)registration / implicit-context checks exercising the same old race, which the guard fixes regardless of the amplifier.

Code Flow

sequenceDiagram
    participant Editor as Active editor change
    participant ICtx as ChatImplicitContext (main)
    participant Svc as LanguageModelIgnoredFilesService (main)
    participant MT as MainThreadLanguageModels (main)
    participant EH as ExtHostLanguageModels (extHost)
    participant Ext as Copilot ignore provider (extHost)

    Note over Ext,EH: register → _ignoredFileProviders.set(handle, provider)
    Editor->>ICtx: focus / selection change
    ICtx->>Svc: fileIsIgnored(uri)
    Svc->>MT: provider.isFileIgnored(uri)
    MT->>EH: $isFileIgnored(handle, uri)  [RPC]
    Note over Ext,EH: provider disposed → _ignoredFileProviders.delete(handle)<br/>($unregisterFileIgnoreProvider still in flight)
    EH-->>EH: _ignoredFileProviders.get(handle) === undefined
    EH-->>MT: BEFORE: throw "Unknown LanguageModelIgnoredFileProvider"
    Note over EH: AFTER: return false (not ignored)
Loading

Affected Files

  • src/vs/workbench/api/common/extHostLanguageModels.tscrash site + fix. $isFileIgnored looks up _ignoredFileProviders.get(handle) and threw when absent; disposal of registerIgnoredFileProvider deletes the entry synchronously.
  • src/vs/workbench/api/browser/mainThreadLanguageModels.ts(context, producer side) $registerFileIgnoreProvider registers a main-thread provider whose isFileIgnored proxies to $isFileIgnored(handle, ...); $unregisterFileIgnoreProvider does the async deleteAndDispose(handle).
  • src/vs/workbench/contrib/chat/common/ignoredFiles.ts(context, precedent) LanguageModelIgnoredFilesService.fileIsIgnored returns false when there is no provider — the safe default this fix mirrors.
  • src/vs/workbench/contrib/chat/browser/attachments/chatImplicitContext.ts(context, trigger) calls ignoredFilesService.fileIsIgnored(uri) on editor changes, the path that drives the RPC during teardown.

Repro Steps

Deterministic reproduction is hard because it is a timing race, but conceptually:

  1. Run with an extension that registers an LM ignored-file provider (e.g. GitHub Copilot Chat via vscode.lm.registerIgnoredFileProvider).
  2. Keep an editor active so ChatImplicitContext periodically calls fileIsIgnored on the active resource.
  3. Cause the provider to be unregistered concurrently — reload the window, restart/crash the extension host, or deactivate/update the extension — so the extHost deletes its _ignoredFileProviders entry while a $isFileIgnored call for that handle is in flight (or the main-thread registration is briefly still live).
  4. The extHost receives $isFileIgnored for a handle it already deleted and (before this fix) throws Unknown LanguageModelIgnoredFileProvider, recorded as an unhandled error.

How the Fix Works

Chosen approach — src/vs/workbench/api/common/extHostLanguageModels.ts: in $isFileIgnored, return false (file not ignored) when the handle is unknown, instead of throwing. This is a guard clause at the inbound RPC (IPC) boundary that returns the documented safe default. An unknown handle is only reachable through the benign teardown race, because handles are allocated in the extHost and are only ever echoed back by the main thread — there is no scenario where an unknown handle indicates a logic bug, so nothing real is being masked. The chosen value is exactly the default the main-thread LanguageModelIgnoredFilesService.fileIsIgnored already returns when no provider is registered, so behavior is consistent on both sides of the boundary.

The fix sits at the boundary that fails rather than at a "data producer", because the real producer (the main-thread caller) lives in another process and cannot be synchronized with: the disposal that removes the local entry and the in-flight RPC are concurrent across processes. After this change, extHostLanguageModels.ts $isFileIgnored cannot emit Unknown LanguageModelIgnoredFileProvider for a torn-down handle because the if (!provider) branch now resolves false instead of throwing. No logService.error call is removed and no try/catch is introduced — the guard is added before the throwing path, following the prefer-a-guard-clause-over-try/catch principle, and the inbound handler tolerates the stale cross-process handle rather than re-emitting an unhandled error.

Alternatives considered:

  • Reorder the extHost disposal ($unregisterFileIgnoreProvider vs. local delete): rejected — it cannot close the window for a $isFileIgnored call already dispatched across the IPC boundary; that call still arrives after the local entry is gone.
  • Awaited unregister handshake before deleting the local entry: rejected — it adds cross-process coupling, still cannot recall an in-flight RPC, and risks leaking the map entry if the main thread never confirms (e.g. on host shutdown).
  • Enrich and re-throw with diagnostic context: rejected — the unknown-handle state is expected and benign, not a real fault, so any unhandled error just re-pollutes telemetry without a bug to diagnose; "not ignored" is the correct semantic answer.

Recommended Owner

@roblourens — owns the Chat participant API and LM Tools API per microsoft/vscode-engineering triage/working-areas.md, authored the feature that introduced this code (PR #231696 / commit bcf6a6af), and is actively committing to the repo (e.g. 2026-06-26). Team-membership and liveness gates both pass.

Generated by errors-fix · 2.3K AIC · ⌖ 85.1 AIC · ⊞ 69.4K ·

… $isFileIgnored (fixes #323156)

An unknown handle in the extHost $isFileIgnored RPC handler is only reachable
via a benign cross-process teardown race: the extHost disposal deletes its
local provider entry synchronously while the main-thread registration is still
live (the unregister RPC is async) or while an isFileIgnored call is already in
flight. Return false (not ignored) for an unknown handle instead of throwing an
unhandled error across the IPC boundary, matching the main-thread service's
default when no provider is registered.

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

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 26, 2026 16:30
@vs-code-engineering vs-code-engineering Bot enabled auto-merge (squash) June 26, 2026 16:30
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-Unknown LanguageModelIgnoredFileProvider

2 participants