From ffc1503cbbad41af28f239fd743820fc9ee0e59b Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Mon, 8 Dec 2025 10:59:54 -0500 Subject: [PATCH 1/3] fix: suppress 'ask promise was ignored' error in handleError The Task.ask() method throws this error as control flow for partial UI updates and superseded asks. While callers handle this with .catch(() => {}), it was leaking through handleError() and being reported to the LLM. This change silently ignores this expected control flow error in handleError() since it's not a real error that needs user or LLM notification. --- .changeset/suppress-ask-ignored-error.md | 13 +++++++++++++ .../assistant-message/presentAssistantMessage.ts | 10 ++++++++++ 2 files changed, 23 insertions(+) create mode 100644 .changeset/suppress-ask-ignored-error.md diff --git a/.changeset/suppress-ask-ignored-error.md b/.changeset/suppress-ask-ignored-error.md new file mode 100644 index 00000000000..3cd0732911c --- /dev/null +++ b/.changeset/suppress-ask-ignored-error.md @@ -0,0 +1,13 @@ +--- +"roo-cline": patch +--- + +Fix "Current ask promise was ignored" error leaking to LLM output + +The `Task.ask()` method throws this error as a control flow mechanism for partial UI updates +and superseded asks. While callers already handle this with `.catch(() => {})`, the error +was leaking through `handleError()` in `presentAssistantMessage.ts`, causing it to be +reported to the LLM and confuse the conversation. + +This change suppresses the error in `handleError()` since it's an expected control flow +signal, not a real error that needs to be reported. diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 8089fdbdef4..d475e2a9019 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -224,6 +224,11 @@ export async function presentAssistantMessage(cline: Task) { } const handleError = async (action: string, error: Error) => { + // Silently ignore "ask promise was ignored" errors - this is an internal control flow + // signal, not an actual error. It occurs when a newer ask supersedes an older one. + if (error.message?.includes("ask promise was ignored")) { + return + } const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` await cline.say( "error", @@ -610,6 +615,11 @@ export async function presentAssistantMessage(cline: Task) { } const handleError = async (action: string, error: Error) => { + // Silently ignore "ask promise was ignored" errors - this is an internal control flow + // signal, not an actual error. It occurs when a newer ask supersedes an older one. + if (error.message?.includes("ask promise was ignored")) { + return + } const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` await cline.say( From c12970a879c35475c1fd97a71bc9a008d0fa6be3 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:52:57 -0500 Subject: [PATCH 2/3] Delete .changeset/suppress-ask-ignored-error.md --- .changeset/suppress-ask-ignored-error.md | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .changeset/suppress-ask-ignored-error.md diff --git a/.changeset/suppress-ask-ignored-error.md b/.changeset/suppress-ask-ignored-error.md deleted file mode 100644 index 3cd0732911c..00000000000 --- a/.changeset/suppress-ask-ignored-error.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"roo-cline": patch ---- - -Fix "Current ask promise was ignored" error leaking to LLM output - -The `Task.ask()` method throws this error as a control flow mechanism for partial UI updates -and superseded asks. While callers already handle this with `.catch(() => {})`, the error -was leaking through `handleError()` in `presentAssistantMessage.ts`, causing it to be -reported to the LLM and confuse the conversation. - -This change suppresses the error in `handleError()` since it's an expected control flow -signal, not a real error that needs to be reported. From 057104bab67ce485c58ff908a7338dd86cd4b12d Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Mon, 8 Dec 2025 15:01:45 -0500 Subject: [PATCH 3/3] refactor: use typed AskIgnoredError with instanceof check - Create AskIgnoredError class for the ask-ignored control flow signal - Throw AskIgnoredError instead of generic Error in Task.ask() - Use instanceof AskIgnoredError for type-safe detection in handleError() This is more robust than string matching because instanceof is type-safe and won't accidentally match unrelated errors. --- .../assistant-message/presentAssistantMessage.ts | 9 +++++---- src/core/task/AskIgnoredError.ts | 15 +++++++++++++++ src/core/task/Task.ts | 8 +++++--- 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 src/core/task/AskIgnoredError.ts diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index d475e2a9019..74dba6ba52c 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -9,6 +9,7 @@ import { defaultModeSlug, getModeBySlug } from "../../shared/modes" import type { ToolParamName, ToolResponse, ToolUse, McpToolUse } from "../../shared/tools" import { Package } from "../../shared/package" import { t } from "../../i18n" +import { AskIgnoredError } from "../task/AskIgnoredError" import { fetchInstructionsTool } from "../tools/FetchInstructionsTool" import { listFilesTool } from "../tools/ListFilesTool" @@ -224,9 +225,9 @@ export async function presentAssistantMessage(cline: Task) { } const handleError = async (action: string, error: Error) => { - // Silently ignore "ask promise was ignored" errors - this is an internal control flow + // Silently ignore AskIgnoredError - this is an internal control flow // signal, not an actual error. It occurs when a newer ask supersedes an older one. - if (error.message?.includes("ask promise was ignored")) { + if (error instanceof AskIgnoredError) { return } const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` @@ -615,9 +616,9 @@ export async function presentAssistantMessage(cline: Task) { } const handleError = async (action: string, error: Error) => { - // Silently ignore "ask promise was ignored" errors - this is an internal control flow + // Silently ignore AskIgnoredError - this is an internal control flow // signal, not an actual error. It occurs when a newer ask supersedes an older one. - if (error.message?.includes("ask promise was ignored")) { + if (error instanceof AskIgnoredError) { return } const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` diff --git a/src/core/task/AskIgnoredError.ts b/src/core/task/AskIgnoredError.ts new file mode 100644 index 00000000000..9c7f0f0ffeb --- /dev/null +++ b/src/core/task/AskIgnoredError.ts @@ -0,0 +1,15 @@ +/** + * Error thrown when an ask promise is superseded by a newer one. + * + * This is used as an internal control flow signal - not an actual error. + * It occurs when multiple asks are sent in rapid succession and an older + * ask is invalidated by a newer one (e.g., during streaming updates). + */ +export class AskIgnoredError extends Error { + constructor(reason?: string) { + super(reason ? `Ask ignored: ${reason}` : "Ask ignored") + this.name = "AskIgnoredError" + // Maintains proper prototype chain for instanceof checks + Object.setPrototypeOf(this, AskIgnoredError.prototype) + } +} diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index d084bf4b924..1cd3f03c263 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -4,6 +4,8 @@ import os from "os" import crypto from "crypto" import EventEmitter from "events" +import { AskIgnoredError } from "./AskIgnoredError" + import { Anthropic } from "@anthropic-ai/sdk" import OpenAI from "openai" import delay from "delay" @@ -983,7 +985,7 @@ export class Task extends EventEmitter implements TaskLike { // whole array in new listener. this.updateClineMessage(lastMessage) // console.log("Task#ask: current ask promise was ignored (#1)") - throw new Error("Current ask promise was ignored (#1)") + throw new AskIgnoredError("updating existing partial") } else { // This is a new partial message, so add it with partial // state. @@ -992,7 +994,7 @@ export class Task extends EventEmitter implements TaskLike { console.log(`Task#ask: new partial ask -> ${type} @ ${askTs}`) await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, partial, isProtected }) // console.log("Task#ask: current ask promise was ignored (#2)") - throw new Error("Current ask promise was ignored (#2)") + throw new AskIgnoredError("new partial") } } else { if (isUpdatingPreviousPartial) { @@ -1146,7 +1148,7 @@ export class Task extends EventEmitter implements TaskLike { // command_output. It's important that when we know an ask could // fail, it is handled gracefully. console.log("Task#ask: current ask promise was ignored") - throw new Error("Current ask promise was ignored") + throw new AskIgnoredError("superseded") } const result = { response: this.askResponse!, text: this.askResponseText, images: this.askResponseImages }