Skip to content
Draft
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
8 changes: 8 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2559,6 +2559,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}

this.consecutiveMistakeCount = 0
// Also reset grace-retry counters so the model gets a fresh start
// after user provides guidance. Without this, the counters remain
// elevated and the very next no-tool-use or empty response would
// immediately show an error and re-increment consecutiveMistakeCount.
this.consecutiveNoToolUseCount = 0
this.consecutiveNoAssistantMessagesCount = 0
}

// Getting verbose details is an expensive operation, it uses ripgrep to
Expand Down Expand Up @@ -3695,10 +3701,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
await this.say("api_req_retried")

// Push the same content back to retry
// Mark that user message was removed so it gets re-added on retry
stack.push({
userContent: currentUserContent,
includeFileDetails: false,
retryAttempt: (currentItem.retryAttempt ?? 0) + 1,
userMessageWasRemoved: true,
})

// Continue to retry the request
Expand Down
64 changes: 64 additions & 0 deletions src/core/task/__tests__/grace-retry-errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,70 @@ describe("Grace Retry Error Handling", () => {
})
})

describe("Grace-retry counter reset on mistake limit", () => {
it("should reset consecutiveNoToolUseCount when consecutiveMistakeCount is reset", () => {
const task = new Task({
provider: mockProvider,
apiConfiguration: mockApiConfig,
task: "test task",
startTask: false,
})

// Simulate the state right after the mistake limit dialog resets consecutiveMistakeCount
// In the actual code, all three counters are reset together
task.consecutiveMistakeCount = 5
task.consecutiveNoToolUseCount = 4
task.consecutiveNoAssistantMessagesCount = 3

// Simulate the reset that happens in the mistake_limit_reached handler
task.consecutiveMistakeCount = 0
task.consecutiveNoToolUseCount = 0
task.consecutiveNoAssistantMessagesCount = 0

expect(task.consecutiveMistakeCount).toBe(0)
expect(task.consecutiveNoToolUseCount).toBe(0)
expect(task.consecutiveNoAssistantMessagesCount).toBe(0)
})

it("should allow grace retry period after mistake limit reset", () => {
const task = new Task({
provider: mockProvider,
apiConfiguration: mockApiConfig,
task: "test task",
startTask: false,
})

// After mistake limit reset, counters should be at 0
// This means the model gets a fresh grace retry period
task.consecutiveNoToolUseCount = 0
task.consecutiveNoAssistantMessagesCount = 0

// First failure after reset: should be grace retry (no error shown)
task.consecutiveNoToolUseCount++
expect(task.consecutiveNoToolUseCount).toBe(1)
expect(task.consecutiveNoToolUseCount >= 2).toBe(false)

task.consecutiveNoAssistantMessagesCount++
expect(task.consecutiveNoAssistantMessagesCount).toBe(1)
expect(task.consecutiveNoAssistantMessagesCount >= 2).toBe(false)
})

it("should show error on second failure after mistake limit reset", () => {
const task = new Task({
provider: mockProvider,
apiConfiguration: mockApiConfig,
task: "test task",
startTask: false,
})

// Simulate first + second failure after reset
task.consecutiveNoToolUseCount = 1
task.consecutiveNoToolUseCount++
expect(task.consecutiveNoToolUseCount).toBe(2)
expect(task.consecutiveNoToolUseCount >= 2).toBe(true)
})
})

describe("Parallel with noToolsUsed error handling", () => {
it("should have separate counters for noToolsUsed and noAssistantMessages", () => {
const task = new Task({
Expand Down
Loading