Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -224,6 +225,11 @@ export async function presentAssistantMessage(cline: Task) {
}

const handleError = async (action: string, error: Error) => {
// 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 instanceof AskIgnoredError) {
return
}
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
await cline.say(
"error",
Expand Down Expand Up @@ -610,6 +616,11 @@ export async function presentAssistantMessage(cline: Task) {
}

const handleError = async (action: string, error: Error) => {
// 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 instanceof AskIgnoredError) {
return
}
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`

await cline.say(
Expand Down
15 changes: 15 additions & 0 deletions src/core/task/AskIgnoredError.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}
8 changes: 5 additions & 3 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -983,7 +985,7 @@ export class Task extends EventEmitter<TaskEvents> 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.
Expand All @@ -992,7 +994,7 @@ export class Task extends EventEmitter<TaskEvents> 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) {
Expand Down Expand Up @@ -1146,7 +1148,7 @@ export class Task extends EventEmitter<TaskEvents> 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 }
Expand Down
Loading