Skip to content

Commit 7902e03

Browse files
authored
fix(amazonq): block completion before active edit is accepted/rejected (#8141)
## Problem Both completion suggestions (ghost text) and edit suggestions (diff view) could appear simultaneously due to a race condition. The EditSuggestionState flag was set too late in the edit display flow, allowing completion requests to check the state, see it as false, and proceed even while an edit was being displayed. ## Solution Fixed the race condition by setting the edit state immediately at the start of displaySvgDecoration() before any async operations: * Set EditSuggestionState to true at the beginning of the edit flow * Add state cleanup when edit cannot be shown (completion already active or invalid patch) * Avoid calling clearDecorations() in displayEditSuggestion() to prevent premature state reset * Check EditSuggestionState before making completion requests in recommendationService.ts. This ensures only one suggestion type is displayed at a time with proper mutual exclusion. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 593815a commit 7902e03

File tree

3 files changed

+23
-8
lines changed

3 files changed

+23
-8
lines changed

packages/amazonq/src/app/inline/EditRendering/displayImage.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,11 @@ export class EditDecorationManager {
136136
newCode: string,
137137
originalCodeHighlightRanges: Array<{ line: number; start: number; end: number }>
138138
): Promise<void> {
139-
await this.clearDecorations(editor)
140-
141-
await setContext('aws.amazonq.editSuggestionActive' as any, true)
142-
EditSuggestionState.setEditSuggestionActive(true)
139+
// Clear old decorations but don't reset state (state is already set in displaySvgDecoration)
140+
editor.setDecorations(this.imageDecorationType, [])
141+
editor.setDecorations(this.removedCodeDecorationType, [])
142+
this.currentImageDecoration = undefined
143+
this.currentRemovedCodeDecorations = []
143144

144145
this.acceptHandler = onAccept
145146
this.rejectHandler = onReject
@@ -313,8 +314,16 @@ export async function displaySvgDecoration(
313314
) {
314315
const originalCode = editor.document.getText()
315316

317+
// Set edit state immediately to prevent race condition with completion requests
318+
await setContext('aws.amazonq.editSuggestionActive' as any, true)
319+
EditSuggestionState.setEditSuggestionActive(true)
320+
316321
// Check if a completion suggestion is currently active - if so, discard edit suggestion
317322
if (inlineCompletionProvider && (await inlineCompletionProvider.isCompletionActive())) {
323+
// Clean up state since we're not showing the edit
324+
await setContext('aws.amazonq.editSuggestionActive' as any, false)
325+
EditSuggestionState.setEditSuggestionActive(false)
326+
318327
// Emit DISCARD telemetry for edit suggestion that can't be shown due to active completion
319328
const params = createDiscardTelemetryParams(session, item)
320329
languageClient.sendNotification('aws/logInlineCompletionSessionResults', params)
@@ -326,6 +335,10 @@ export async function displaySvgDecoration(
326335

327336
const isPatchValid = applyPatch(editor.document.getText(), item.insertText as string)
328337
if (!isPatchValid) {
338+
// Clean up state since we're not showing the edit
339+
await setContext('aws.amazonq.editSuggestionActive' as any, false)
340+
EditSuggestionState.setEditSuggestionActive(false)
341+
329342
const params = createDiscardTelemetryParams(session, item)
330343
// TODO: this session is closed on flare side hence discarded is not emitted in flare
331344
languageClient.sendNotification('aws/logInlineCompletionSessionResults', params)

packages/amazonq/src/app/inline/recommendationService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export class RecommendationService {
138138
* Completions use PartialResultToken with single 1 call of [getAllRecommendations].
139139
* Edits leverage partialResultToken to achieve EditStreak such that clients can pull all continuous suggestions generated by the model within 1 EOS block.
140140
*/
141-
if (!isTriggerByDeletion && !request.partialResultToken) {
141+
if (!isTriggerByDeletion && !request.partialResultToken && !EditSuggestionState.isEditSuggestionActive()) {
142142
const completionPromise: Promise<InlineCompletionListWithReferences> = languageClient.sendRequest(
143143
inlineCompletionWithReferencesRequestType.method,
144144
request,

packages/amazonq/test/unit/amazonq/apps/inline/recommendationService.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ describe('RecommendationService', () => {
333333
}
334334
})
335335

336-
it('should make completion request when edit suggestion is active', async () => {
336+
it('should not make completion request when edit suggestion is active', async () => {
337337
// Mock EditSuggestionState to return true (edit suggestion is active)
338338
sandbox.stub(EditSuggestionState, 'isEditSuggestionActive').returns(true)
339339

@@ -360,13 +360,15 @@ describe('RecommendationService', () => {
360360
const completionCalls = cs.filter((c) => c.firstArg === completionApi)
361361
const editCalls = cs.filter((c) => c.firstArg === editApi)
362362

363-
assert.strictEqual(cs.length, 2) // Only edit call
364-
assert.strictEqual(completionCalls.length, 1) // No completion calls
363+
assert.strictEqual(cs.length, 1) // Only edit call
364+
assert.strictEqual(completionCalls.length, 0) // No completion calls
365365
assert.strictEqual(editCalls.length, 1) // One edit call
366366
})
367367

368368
it('should make completion request when edit suggestion is not active', async () => {
369369
// Mock EditSuggestionState to return false (no edit suggestion active)
370+
sandbox.stub(EditSuggestionState, 'isEditSuggestionActive').returns(false)
371+
370372
const mockResult = {
371373
sessionId: 'test-session',
372374
items: [mockInlineCompletionItemOne],

0 commit comments

Comments
 (0)