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
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-patternnew Promise(r => generator.generateTerminalQuickFix(...).then(fixes => { ...; r(picks); })). The.then()has no rejection handler.generateTerminalQuickFixlegitimately throws when a chat fetch returns a non-success type (network/model/rate-limit/filtered). With no rejection handler two things break at once:unhandlederrorin error telemetry.r(picks)is never called → the outerpicksPromisenever settles →await picksPromisehangs 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:
@meganroggeCulprit Commit
Not a regression introduced by a single commit — this is a pre-existing defect. Evidence:
ef061ccb(2026‐05‐14,@vritant24), only renamed the model idcopilot-fast→copilot-utility-small; it did not introduce the throw or the missing rejection handler.git blamewas 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...'"]Affected Files
extensions/copilot/src/extension/conversation/vscode-node/terminalFixGenerator.ts—generateTerminalFixes(the fire-and-forget caller). The fix adds a rejection handler to the.then()at lines 61–89. The throw sites at:184-186and:240-242(insideTerminalQuickFixGenerator) are intentional failure signaling and are not modified.Repro Steps
github.copilot.terminal.fixTerminalLastCommand.makeChatRequestyieldsfetchResult.type !== 'success'.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 approach —
extensions/copilot/src/extension/conversation/vscode-node/terminalFixGenerator.ts,generateTerminalFixes:Add an
onRejectedhandler as the second argument to the existing.then(fixes => { ... })(bypass/defect site: the handler-less.then()atterminalFixGenerator.ts:61):Why this is the correct fix:
.then()), not at the crash site. The throw inside_generateTerminalQuickFixFileContextis 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.ILogService.error(err)(nologService.errorcall is removed, notry/catchis 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.r([])letsawait picksPromisecomplete, so the quick pick leaves the "Generating..." state instead of hanging. It reuses the existingpicks.length === 0→ "No fixes found" path (lines 91–95), so no new UX is introduced.:184-186and:240-242), since both propagate through the same single.then().ILogServiceis already imported in the file, andinstantiationService.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:
throwat:240-242into areturn— 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.await picksPromise(line 108) intry/catch— rejected: it cannot work, because on failurer(picks)is never called sopicksPromisenever settles;awaithangs rather than throwing, so a surroundingtry/catchwould never run.Recommended Owner
@meganrogge— she is the sole owner of theterminal-quick-fixarea 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 tomicrosoft/vscodeis 2026‐06‐25, actively committing around the bucket's 2026‐06‐24 first-seen date; membership — she authors commits merged directly intomicrosoft/vscodeand is listed as an owner in the team triage doc.