Skip to content

fix: handle snippy post-insertion rejection to avoid unhandled error (fixes #331807) #331818

Description

@vs-code-engineering

Summary

The Copilot code-referencing ("snippy") post-insertion check is fired as an unawaited, uncaught floating promise from NextEditProvider.runSnippy. When the snippy service returns a non-200 response, SnippyFetchService.fetch throws, handlePostInsertion re-throws, and — because the promise is neither awaited nor given a rejection handler — the error surfaces as an unhandled rejection that is reported to error telemetry. The specific 400 seen here (source too short (must be at least 110 tokens, but is 105)) is an expected server-side rejection: the client's local pre-check uses a 65-lexeme minimum while the service enforces its own, higher token minimum, so short accepted suggestions routinely trip it. Impact is noisy telemetry only — no user-facing functionality breaks.

Fixes #331807
Recommended reviewer: @ulugbekna

Culprit Commit

Field Value
Commit 333d9a4053e (vendored-import boundary)
Author @not-determined
PR n/a
Message Initial import of the Copilot Chat extension ("Hello Copilot")
Why The fire-and-forget handlePostInsertion(...) call in runSnippy has existed without a rejection handler since the extension was vendored into microsoft/vscode. git blame shows the throwing call site predates all recent changes, so this is a pre-existing bug re-bucketed by telemetry (the specific 400 message text changed the bucket), not a new regression.

Pre-existing / re-bucketing: the underlying floating-promise pattern is not introduced by any recent commit in the regression window; the new bucket is driven by the server's error-message text, not by a change in triggering logic.

Code Flow

sequenceDiagram
    participant User as User accepts NES
    participant NEP as NextEditProvider.runSnippy
    participant Svc as SnippyService.handlePostInsertion
    participant Fetch as SnippyFetchService.fetch
    participant Server as Snippy service

    User->>NEP: handleAcceptance()
    Note over NEP: ⚠️ Root cause:<br/>handlePostInsertion() called<br/>without await / .catch
    NEP->>Svc: handlePostInsertion(uri, doc, edit)
    Svc->>Fetch: fetchMatch(source)
    Fetch->>Server: POST snippy/match
    Server-->>Fetch: 400 "source too short (110 > 105)"
    Note over Fetch: 💥 throw new Error(`Failed with status 400 ...`)
    Fetch-->>Svc: rejects
    Svc-->>NEP: rejects (floating promise)
    Note over NEP: Unhandled rejection → error telemetry
Loading

Affected Files

File Role Evidence
extensions/copilot/src/extension/inlineEdits/node/nextEditProvider.ts root cause / fix site L1648-1653: this._snippyService.handlePostInsertion(...) called with no await and no .catch, so a rejection escapes as an unhandled error
extensions/copilot/src/platform/snippy/common/snippyServiceImpl.ts rethrow site L40-44: catch (e) { throw e; } re-throws the fetch failure up to the floating caller
extensions/copilot/src/platform/snippy/common/snippyFetcher.ts crash site L70-71: if (fetchResponse.status !== 200) { throw new Error(\Failed with status ${status} ...`); }`
extensions/copilot/src/platform/snippy/common/snippyCompute.ts contributing mismatch L8: MinTokenLength = 65 — client pre-check floor is looser than the service's 110-token floor, so short sources pass the client gate but are rejected by the server

Repro Steps

  1. Use the Copilot Chat extension with Next Edit Suggestions (code referencing / public-code matching enabled).
  2. Accept a short next-edit suggestion whose surrounding source clears the client's local 65-lexeme pre-check but is below the snippy service's token minimum (~110 tokens).
  3. runSnippy fires handlePostInsertion; the snippy /match request returns HTTP 400 (source too short).
  4. Because the promise is unawaited and uncaught, the rejection surfaces as an unhandled error and is reported to telemetry.

Deterministic given a source between the two thresholds; otherwise triggered whenever the service rejects the request for any reason.

How the Fix Works

Chosen approach (nextEditProvider.ts): runSnippy now awaits handlePostInsertion(...) inside a try/catch and routes any failure to this._logService.error(e, ...). This attaches a rejection handler at the fire-and-forget boundary that owns the background operation, so the failure is recorded as a handled error through the existing log/telemetry pipeline instead of escaping as an unhandled rejection. This follows the principle of fixing the error at the boundary that owns the best-effort background work — snippy code-referencing is explicitly a best-effort check, so its failures should not appear as unhandled application errors. The logService.error call preserves telemetry visibility of genuine failures; nothing is silently swallowed.

Alternatives considered:

  • Aligning the client MinTokenLength (65) with the server's 110-token floor — rejected: the exact server threshold is not a stable contract the client can safely hardcode, other non-200 responses would still leak, and it would not fix the underlying missing-rejection-handler defect.
  • Swallowing the error inside snippyServiceImpl.handlePostInsertion (removing the rethrow) — rejected: that hides all fetch failures from callers/telemetry rather than handling them at the fire-and-forget site, and would violate the "never silence errors" principle by dropping them entirely.

Recommended Owner

@ulugbekna — dominant recent author of nextEditProvider.ts (author of the most recent NES/inline-edits commits touching this file, merged into microsoft/vscode), making them the natural owner for this fire-and-forget call site in the inline-edits area.

Generated by errors-fix · opus48 · 461.5 AIC · ⌖ 11.7 AIC · ⊞ 18.6K ·


Note

This was originally intended as a pull request, but PR creation failed. The changes have been pushed to the branch fix/snippy-post-insertion-unhandled-rejection-aa290b8d76b80828.

Original error: ERR_API: [2026-08-20T15:39:49.753Z] create pull request in microsoft/vscode failed (attempt 1)

Original error: Validation Failed: {"resource":"PullRequest","code":"custom","field":"fork_collab","message":"fork_collab Fork collab can't be granted by someone without permission"} - https://docs.github.com/rest/pulls/pulls#create-a-pull-request
Retryable: false
Suggestion: This error cannot be resolved by retrying. Please check the error details and fix the underlying issue.

To create the pull request manually:

gh pr create --title "fix: handle snippy post-insertion rejection to avoid unhandled error (fixes #331807)" --base main --head vscodebot-pr:fix/snippy-post-insertion-unhandled-rejection-aa290b8d76b80828 --repo microsoft/vscode
Show patch (36 lines)
From ff4844410264c7d9af7014396dbcb4e1267048c9 Mon Sep 17 00:00:00 2001
X-GH-AW-Base-Commit: 680caf21e40b5ede66ded8544b689fb2c9fe525f
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 15:21:08 +0000
Subject: [PATCH] fix: handle snippy post-insertion rejection to avoid
 unhandled error

---
 .../src/extension/inlineEdits/node/nextEditProvider.ts | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/extensions/copilot/src/extension/inlineEdits/node/nextEditProvider.ts b/extensions/copilot/src/extension/inlineEdits/node/nextEditProvider.ts
index 73c7e94b542..70f386be0d7 100644
--- a/extensions/copilot/src/extension/inlineEdits/node/nextEditProvider.ts
+++ b/extensions/copilot/src/extension/inlineEdits/node/nextEditProvider.ts
@@ -1649,7 +1649,15 @@ export class NextEditProvider extends Disposable implements INextEditProvider<Ne
 		if (suggestion.result === undefined || suggestion.result.edit === undefined) {
 			return;
 		}
-		this._snippyService.handlePostInsertion(docId.toUri(), suggestion.result.documentBeforeEdits, suggestion.result.edit);
+		// Snippy code-referencing is a best-effort, fire-and-forget check. It can fail for
+		// expected reasons (e.g. the service rejecting a source that is shorter than its own
+		// minimum token count), so route any failure to the log service instead of letting the
+		// unawaited promise reject and surface as an unhandled error in telemetry.
+		try {
+			await this._snippyService.handlePostInsertion(docId.toUri(), suggestion.result.documentBeforeEdits, suggestion.result.edit);
+		} catch (e) {
+			this._logService.error(e, 'Snippy post-insertion check failed');
+		}
 	}
 
 	private _isRejectedEditMemoryEnabled(): boolean {
-- 
2.54.0

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions