Skip to content

Commit 057104b

Browse files
committed
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.
1 parent c12970a commit 057104b

File tree

3 files changed

+25
-7
lines changed

3 files changed

+25
-7
lines changed

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { defaultModeSlug, getModeBySlug } from "../../shared/modes"
99
import type { ToolParamName, ToolResponse, ToolUse, McpToolUse } from "../../shared/tools"
1010
import { Package } from "../../shared/package"
1111
import { t } from "../../i18n"
12+
import { AskIgnoredError } from "../task/AskIgnoredError"
1213

1314
import { fetchInstructionsTool } from "../tools/FetchInstructionsTool"
1415
import { listFilesTool } from "../tools/ListFilesTool"
@@ -224,9 +225,9 @@ export async function presentAssistantMessage(cline: Task) {
224225
}
225226

226227
const handleError = async (action: string, error: Error) => {
227-
// Silently ignore "ask promise was ignored" errors - this is an internal control flow
228+
// Silently ignore AskIgnoredError - this is an internal control flow
228229
// signal, not an actual error. It occurs when a newer ask supersedes an older one.
229-
if (error.message?.includes("ask promise was ignored")) {
230+
if (error instanceof AskIgnoredError) {
230231
return
231232
}
232233
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`
@@ -615,9 +616,9 @@ export async function presentAssistantMessage(cline: Task) {
615616
}
616617

617618
const handleError = async (action: string, error: Error) => {
618-
// Silently ignore "ask promise was ignored" errors - this is an internal control flow
619+
// Silently ignore AskIgnoredError - this is an internal control flow
619620
// signal, not an actual error. It occurs when a newer ask supersedes an older one.
620-
if (error.message?.includes("ask promise was ignored")) {
621+
if (error instanceof AskIgnoredError) {
621622
return
622623
}
623624
const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}`

src/core/task/AskIgnoredError.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Error thrown when an ask promise is superseded by a newer one.
3+
*
4+
* This is used as an internal control flow signal - not an actual error.
5+
* It occurs when multiple asks are sent in rapid succession and an older
6+
* ask is invalidated by a newer one (e.g., during streaming updates).
7+
*/
8+
export class AskIgnoredError extends Error {
9+
constructor(reason?: string) {
10+
super(reason ? `Ask ignored: ${reason}` : "Ask ignored")
11+
this.name = "AskIgnoredError"
12+
// Maintains proper prototype chain for instanceof checks
13+
Object.setPrototypeOf(this, AskIgnoredError.prototype)
14+
}
15+
}

src/core/task/Task.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import os from "os"
44
import crypto from "crypto"
55
import EventEmitter from "events"
66

7+
import { AskIgnoredError } from "./AskIgnoredError"
8+
79
import { Anthropic } from "@anthropic-ai/sdk"
810
import OpenAI from "openai"
911
import delay from "delay"
@@ -983,7 +985,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
983985
// whole array in new listener.
984986
this.updateClineMessage(lastMessage)
985987
// console.log("Task#ask: current ask promise was ignored (#1)")
986-
throw new Error("Current ask promise was ignored (#1)")
988+
throw new AskIgnoredError("updating existing partial")
987989
} else {
988990
// This is a new partial message, so add it with partial
989991
// state.
@@ -992,7 +994,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
992994
console.log(`Task#ask: new partial ask -> ${type} @ ${askTs}`)
993995
await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, partial, isProtected })
994996
// console.log("Task#ask: current ask promise was ignored (#2)")
995-
throw new Error("Current ask promise was ignored (#2)")
997+
throw new AskIgnoredError("new partial")
996998
}
997999
} else {
9981000
if (isUpdatingPreviousPartial) {
@@ -1146,7 +1148,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
11461148
// command_output. It's important that when we know an ask could
11471149
// fail, it is handled gracefully.
11481150
console.log("Task#ask: current ask promise was ignored")
1149-
throw new Error("Current ask promise was ignored")
1151+
throw new AskIgnoredError("superseded")
11501152
}
11511153

11521154
const result = { response: this.askResponse!, text: this.askResponseText, images: this.askResponseImages }

0 commit comments

Comments
 (0)