From 639c5e546786a5de8012ce97099c6f634719618a Mon Sep 17 00:00:00 2001 From: 0xMink <260166390+0xMink@users.noreply.github.com> Date: Fri, 22 May 2026 08:38:43 +0000 Subject: [PATCH] chore: enforce no-floating-promises in core/task/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Next slice of the no-floating-promises ratchet — widens the rule's scope in eslint.config.mjs to core/task/**. core/task/ had 25 un-awaited promises. Task.ts (19): all genuine fire-and-forget, marked void — task-loop kickoffs in the constructor and start(), UI posts in synchronous/streaming/setTimeout paths, the explicitly-background checkpoint init, and 9 presentAssistantMessage calls (a self-locking streaming presenter designed for fire-and-forget). void preserves current behavior, and the file already uses void this.updateClineMessage(...) elsewhere. Task.spec.ts (6): un-awaited task.submitUserMessage(...) calls in async tests, now awaited. --- src/core/task/Task.ts | 38 ++++++++++++++-------------- src/core/task/__tests__/Task.spec.ts | 12 ++++----- src/eslint.config.mjs | 2 +- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index fcdfd0263d..bf252b650d 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -524,7 +524,7 @@ export class Task extends EventEmitter implements TaskLike { this.messageQueueStateChangedHandler = () => { this.emit(RooCodeEventName.TaskUserMessage, this.taskId) this.emit(RooCodeEventName.QueuedMessagesUpdated, this.taskId, this.messageQueueService.messages) - this.providerRef.deref()?.postStateToWebviewWithoutTaskHistory() + void this.providerRef.deref()?.postStateToWebviewWithoutTaskHistory() } this.messageQueueService.on("stateChanged", this.messageQueueStateChangedHandler) @@ -569,9 +569,9 @@ export class Task extends EventEmitter implements TaskLike { if (startTask) { this._started = true if (task || images) { - this.startTask(task, images) + void this.startTask(task, images) } else if (historyItem) { - this.resumeTaskFromHistory() + void this.resumeTaskFromHistory() } else { throw new Error("Either historyItem or task/images must be provided") } @@ -1153,7 +1153,7 @@ export class Task extends EventEmitter implements TaskLike { // data or one whole message at a time so ignore partial for // saves, and only post parts of partial message instead of // whole array in new listener. - this.updateClineMessage(lastMessage) + void this.updateClineMessage(lastMessage) // console.log("Task#ask: current ask promise was ignored (#1)") throw new AskIgnoredError("updating existing partial") } else { @@ -1191,7 +1191,7 @@ export class Task extends EventEmitter implements TaskLike { lastMessage.progressStatus = progressStatus lastMessage.isProtected = isProtected await this.saveClineMessages() - this.updateClineMessage(lastMessage) + void this.updateClineMessage(lastMessage) } else { // This is a new and complete message, so add it like normal. this.askResponse = undefined @@ -1253,7 +1253,7 @@ export class Task extends EventEmitter implements TaskLike { if (message) { this.interactiveAsk = message this.emit(RooCodeEventName.TaskInteractive, this.taskId) - provider?.postMessageToWebview({ type: "interactionRequired" }) + void provider?.postMessageToWebview({ type: "interactionRequired" }) } }, statusMutationTimeout), ) @@ -1636,7 +1636,7 @@ export class Task extends EventEmitter implements TaskLike { lastMessage.images = images lastMessage.partial = partial lastMessage.progressStatus = progressStatus - this.updateClineMessage(lastMessage) + void this.updateClineMessage(lastMessage) } else { // This is a new partial message, so add it with partial state. const sayTs = Date.now() @@ -1675,7 +1675,7 @@ export class Task extends EventEmitter implements TaskLike { await this.saveClineMessages() // More performant than an entire `postStateToWebview`. - this.updateClineMessage(lastMessage) + void this.updateClineMessage(lastMessage) } else { // This is a new and complete message, so add it like normal. const sayTs = Date.now() @@ -1784,7 +1784,7 @@ export class Task extends EventEmitter implements TaskLike { const { task, images } = this.metadata if (task || images) { - this.startTask(task ?? undefined, images ?? undefined) + void this.startTask(task ?? undefined, images ?? undefined) } } @@ -2325,7 +2325,7 @@ export class Task extends EventEmitter implements TaskLike { private async initiateTaskLoop(userContent: Anthropic.Messages.ContentBlockParam[]): Promise { // Kicks off the checkpoints initialization process in the background. - getCheckpointService(this) + void getCheckpointService(this) let nextUserContent = userContent let includeFileDetails = true @@ -2768,7 +2768,7 @@ export class Task extends EventEmitter implements TaskLike { // Add to content and present this.assistantMessageContent.push(partialToolUse) this.userMessageContentReady = false - presentAssistantMessage(this) + void presentAssistantMessage(this) } else if (event.type === "tool_call_delta") { // Process chunk using streaming JSON parser const partialToolUse = NativeToolCallParser.processStreamingChunk( @@ -2787,7 +2787,7 @@ export class Task extends EventEmitter implements TaskLike { this.assistantMessageContent[toolUseIndex] = partialToolUse // Present updated tool use - presentAssistantMessage(this) + void presentAssistantMessage(this) } } } else if (event.type === "tool_call_end") { @@ -2813,7 +2813,7 @@ export class Task extends EventEmitter implements TaskLike { this.userMessageContentReady = false // Present the finalized tool call - presentAssistantMessage(this) + void presentAssistantMessage(this) } else if (toolUseIndex !== undefined) { // finalizeStreamingToolCall returned null (malformed JSON or missing args) // Mark the tool as non-partial so it's presented as complete, but execution @@ -2832,7 +2832,7 @@ export class Task extends EventEmitter implements TaskLike { this.userMessageContentReady = false // Present the tool call - validation will handle missing params - presentAssistantMessage(this) + void presentAssistantMessage(this) } } } @@ -2865,7 +2865,7 @@ export class Task extends EventEmitter implements TaskLike { // Present the tool call to user - presentAssistantMessage will execute // tools sequentially and accumulate all results in userMessageContent - presentAssistantMessage(this) + void presentAssistantMessage(this) break } case "text": { @@ -2884,7 +2884,7 @@ export class Task extends EventEmitter implements TaskLike { }) this.userMessageContentReady = false } - presentAssistantMessage(this) + void presentAssistantMessage(this) break } } @@ -3232,7 +3232,7 @@ export class Task extends EventEmitter implements TaskLike { this.userMessageContentReady = false // Present the finalized tool call - presentAssistantMessage(this) + void presentAssistantMessage(this) } else if (toolUseIndex !== undefined) { // finalizeStreamingToolCall returned null (malformed JSON or missing args) // We still need to mark the tool as non-partial so it gets executed @@ -3251,7 +3251,7 @@ export class Task extends EventEmitter implements TaskLike { this.userMessageContentReady = false // Present the tool call - validation will handle missing params - presentAssistantMessage(this) + void presentAssistantMessage(this) } } } @@ -3450,7 +3450,7 @@ export class Task extends EventEmitter implements TaskLike { // If there is content to update then it will complete and // update `this.userMessageContentReady` to true, which we // `pWaitFor` before making the next request. - presentAssistantMessage(this) + void presentAssistantMessage(this) } if (hasTextContent || hasToolUses) { diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index 6a65c858f9..71064d7b7d 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -1391,7 +1391,7 @@ describe("Cline", () => { ] // Call submitUserMessage - task.submitUserMessage("test message", ["image1.png"]) + await task.submitUserMessage("test message", ["image1.png"]) // Verify handleWebviewAskResponse was called directly (not webview) expect(handleResponseSpy).toHaveBeenCalledWith("messageResponse", "test message", ["image1.png"]) @@ -1411,13 +1411,13 @@ describe("Cline", () => { const handleResponseSpy = vi.spyOn(task, "handleWebviewAskResponse") // Call with empty text and no images - task.submitUserMessage("", []) + await task.submitUserMessage("", []) // Should not call handleWebviewAskResponse for empty messages expect(handleResponseSpy).not.toHaveBeenCalled() // Call with whitespace only - task.submitUserMessage(" ", []) + await task.submitUserMessage(" ", []) expect(handleResponseSpy).not.toHaveBeenCalled() }) @@ -1434,7 +1434,7 @@ describe("Cline", () => { // Test with no messages (new task scenario) task.clineMessages = [] - task.submitUserMessage("new task", ["image1.png"]) + await task.submitUserMessage("new task", ["image1.png"]) expect(handleResponseSpy).toHaveBeenCalledWith("messageResponse", "new task", ["image1.png"]) @@ -1450,7 +1450,7 @@ describe("Cline", () => { text: "Initial message", }, ] - task.submitUserMessage("follow-up message", ["image2.png"]) + await task.submitUserMessage("follow-up message", ["image2.png"]) expect(handleResponseSpy).toHaveBeenCalledWith("messageResponse", "follow-up message", ["image2.png"]) }) @@ -1477,7 +1477,7 @@ describe("Cline", () => { const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) // Should log error but not throw - task.submitUserMessage("test message") + await task.submitUserMessage("test message") expect(consoleErrorSpy).toHaveBeenCalledWith("[Task#submitUserMessage] Provider reference lost") expect(handleResponseSpy).not.toHaveBeenCalled() diff --git a/src/eslint.config.mjs b/src/eslint.config.mjs index 6441a0cfd9..0b3a49b4f5 100644 --- a/src/eslint.config.mjs +++ b/src/eslint.config.mjs @@ -32,7 +32,7 @@ export default [ { // Ratchet: enforce no-floating-promises directory by directory. Each // directory is added here once its floating promises are resolved. - files: ["activate/**/*.ts"], + files: ["activate/**/*.ts", "core/task/**/*.ts"], languageOptions: { parserOptions: { project: true,