Skip to content

fix: handle failed terminal quick fix model request to avoid unhandled rejection (fixes #323149)#323154

Open
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/terminal-quickfix-unhandled-rejection-323149-e9b36a1dc042c1ed
Open

fix: handle failed terminal quick fix model request to avoid unhandled rejection (fixes #323149)#323154
vs-code-engineering[bot] wants to merge 1 commit into
mainfrom
fix/terminal-quickfix-unhandled-rejection-323149-e9b36a1dc042c1ed

Conversation

@vs-code-engineering

Copy link
Copy Markdown
Contributor

Summary

The Copilot terminal quick fix command (github.copilot.terminal.fixTerminalLastCommand) reports an unhandled promise rejection to error telemetry — Encountered an error while fetching quick fix file context: failed — whenever the underlying chat/model request returns a non-success result.

In generateTerminalFixes (terminalFixGenerator.ts), the result picks are produced with the explicit-promise anti-pattern new Promise(r => generator.generateTerminalQuickFix(...).then(fixes => { ...; r(picks); })). The .then() has no rejection handler. generateTerminalQuickFix legitimately throws when a chat fetch returns a non-success type (network/model/rate-limit/filtered). With no rejection handler two things break at once:

  1. The rejection is never handled → surfaced as an unhandlederror in error telemetry.
  2. r(picks) is never called → the outer picksPromise never settles → await picksPromise hangs forever and the quick pick is stranded on "Generating...".

The fix adds a rejection handler at the fire-and-forget boundary that logs the failure and resolves with an empty result, reusing the existing "No fixes found" UX. The intentional throw at the failure site is left untouched so the failure is still observable.

Fixes #323149
Recommended reviewer: @meganrogge

Culprit Commit

Not a regression introduced by a single commit — this is a pre-existing defect. Evidence:

  • The error bucket has hits spread across every shipped extension version (0.46.2 → 0.53.1), with first-seen timestamps clustered inside a single ~6‐hour window on 2026‐06‐24, concentrated in 0.52.0 and 0.53.1. That signature is a transient backend/model failure spike that simultaneously exercised an always-present-but-rarely-hit failure path across all versions — not a code change landing in one version.
  • The most recent change to the file, ef061ccb (2026‐05‐14, @vritant24), only renamed the model id copilot-fastcopilot-utility-small; it did not introduce the throw or the missing rejection handler.
  • A precise git blame was not possible in this environment (shallow clone, fetch-depth=1), but the cross-version bucket history is sufficient to classify the missing-handler pattern as long-standing.

So: the trigger was a transient spike of non-success chat fetch results on 2026‐06‐24; the root-cause code defect is the missing rejection handler that turns that expected failure into an unhandled rejection plus a stuck quick pick.

Code Flow

flowchart TD
    A["User: run command that fails in terminal"] --> B["Command github.copilot.terminal.fixTerminalLastCommand<br/>(conversationFeature.ts:279)"]
    B --> C["generateTerminalFixes(instantiationService)<br/>terminalFixGenerator.ts:54"]
    C --> D["picksPromise = new Promise(r =>...)<br/>:60"]
    D --> E[".generateTerminalQuickFix(...).then(fixes => ...)<br/>:61  — NO rejection handler"]
    E --> F["generateTerminalQuickFix(...)<br/>:136"]
    F --> G["_generateTerminalQuickFixFileContext(...)<br/>:218"]
    G --> H["endpoint.makeChatRequest(...)<br/>:229"]
    H --> I{"fetchResult.type === 'success'?"}
    I -- "no (e.g. 'failed')" --> J["throw new Error('Encountered an error<br/>while fetching quick fix file context: failed')<br/>:240-241"]
    J --> K["rejection propagates up through<br/>generateTerminalQuickFix (await at :137)"]
    K --> L["reaches .then() with no onRejected<br/>:61"]
    L --> M["UNHANDLED REJECTION → error telemetry"]
    L --> N["r(picks) never called → picksPromise never settles"]
    N --> O["await picksPromise hangs :108 → quick pick stuck on 'Generating...'"]
Loading

Affected Files

  • extensions/copilot/src/extension/conversation/vscode-node/terminalFixGenerator.tsgenerateTerminalFixes (the fire-and-forget caller). The fix adds a rejection handler to the .then() at lines 61–89. The throw sites at :184-186 and :240-242 (inside TerminalQuickFixGenerator) are intentional failure signaling and are not modified.

Repro Steps

  1. Run a command in the integrated terminal that fails (so a terminal command-match result is available).
  2. Invoke the Copilot terminal quick fix command github.copilot.terminal.fixTerminalLastCommand.
  3. Force the model/chat request to return a non-success result (e.g. simulate a network/model failure or trigger it during a backend outage such as the 2026‐06‐24 spike), so makeChatRequest yields fetchResult.type !== 'success'.
  4. Observe: an unhandled promise rejection (Encountered an error while fetching quick fix file context: failed) is reported to error telemetry, and the quick pick stays stuck showing "Generating..." indefinitely.

With the fix: the failure is logged via ILogService.error, the quick pick resolves to the existing "No fixes found" state, and no unhandled rejection is reported.

How the Fix Works

Chosen approachextensions/copilot/src/extension/conversation/vscode-node/terminalFixGenerator.ts, generateTerminalFixes:

Add an onRejected handler as the second argument to the existing .then(fixes => { ... }) (bypass/defect site: the handler-less .then() at terminalFixGenerator.ts:61):

}, err => {
    // Generating terminal fixes is best-effort: a failed model request rejects here.
    // Surface it as "No fixes found" instead of leaving an unhandled rejection that
    // also strands the quick pick on "Generating...".
    instantiationService.invokeFunction(accessor => accessor.get(ILogService)).error(err);
    r([]);
});

Why this is the correct fix:

  • It fixes the defect at the fire-and-forget boundary where the rejection actually escapes (the handler-less .then()), not at the crash site. The throw inside _generateTerminalQuickFixFileContext is deliberate failure signaling for an expected runtime condition; suppressing it there would hide a real failure from the rest of the method and from telemetry. The right place to decide "what the user sees when fix generation fails" is the command handler.
  • It is not error-masking: the failure is still recorded via ILogService.error(err) (no logService.error call is removed, no try/catch is added around the throwing code). Terminal quick fix is genuinely best-effort and has a defined empty-result UX, so converting a failed generation into "No fixes found" is the intended user-visible behavior.
  • It fixes the second, user-visible half of the bug: resolving the outer promise with r([]) lets await picksPromise complete, so the quick pick leaves the "Generating..." state instead of hanging. It reuses the existing picks.length === 0 → "No fixes found" path (lines 91–95), so no new UX is introduced.
  • It covers both throw sites (:184-186 and :240-242), since both propagate through the same single .then().
  • ILogService is already imported in the file, and instantiationService.invokeFunction(accessor => accessor.get(...)) is the established service-access pattern in this folder (e.g. conversationFeature.ts), so the change is idiomatic and minimal (6 lines added, 0 removed).

Alternatives considered:

  • Change the throw at :240-242 into a return — rejected: it modifies the crash site (the bottom of the stack), removes legitimate failure signaling, and only addresses one of the two throw sites while leaving the unhandled-rejection pattern in place for the other.
  • Wrap await picksPromise (line 108) in try/catch — rejected: it cannot work, because on failure r(picks) is never called so picksPromise never settles; await hangs rather than throwing, so a surrounding try/catch would never run.

Recommended Owner

@meganrogge — she is the sole owner of the terminal-quick-fix area and owns the Copilot terminal chat integration (inline terminal chat, chat-terminal) per the VS Code team working-areas mapping, which is exactly this feature (github.copilot.terminal.fixTerminalLastCommand). Both ownership gates pass: liveness — her most recent commit to microsoft/vscode is 2026‐06‐25, actively committing around the bucket's 2026‐06‐24 first-seen date; membership — she authors commits merged directly into microsoft/vscode and is listed as an owner in the team triage doc.

Generated by errors-fix · 2.5K AIC · ⌖ 63.6 AIC · ⊞ 69.4K ·

…d rejection (fixes #323149)

The command github.copilot.terminal.fixTerminalLastCommand builds picksPromise via new Promise(r => generateTerminalQuickFix(...).then(fixes => { ...; r(picks); })). The .then() had no rejection handler, so when generateTerminalQuickFix rejected (it throws when a chat fetch returns a non-success result) two failures occurred: an unhandled promise rejection reported to error telemetry, and r(picks) never ran so picksPromise never settled and the quick pick was stranded on Generating. Add a rejection handler at the fire-and-forget boundary that logs the error and resolves with an empty result, reusing the existing No fixes found UX. The intentional throw at the failure site is left untouched.

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

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:07
@vs-code-engineering vs-code-engineering Bot enabled auto-merge (squash) June 26, 2026 16:07
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] [GitHub.copilot-chat] unhandlederror-Encountered an error while fetching quick fix file context: failed

2 participants