Repository: RooVetGit/Roo-Code
Pull request #3156: Fix issues with subtasks attempting completion along with commands
Author: login: mrubens
Labels: name: bug
description: Something isn't working
name: size:M
description: This PR changes 30-99 lines, ignoring generated files.
name: lgtm
description: This PR has been approved by a maintainer
Created: 2025-05-04T03:42:57Z | Merged: 2025-05-04T06:57:19Z


<!-- ELLIPSIS_HIDDEN -->


> [!IMPORTANT]
> Fixes command execution and task completion handling, refactors code, and updates UI components for better task management and auto-approval.
> 
>   - **Behavior**:
>     - Fixes handling of command execution and task completion in `attemptCompletionTool()` and `executeCommandTool()`.
>     - Updates `autoApprove` logic in `ChatView.tsx` to handle auto-approval of commands and tools.
>   - **Refactoring**:
>     - Removes `id` from `progressStatus` in `roo-code.d.ts`, `types.ts`, and `index.ts`.
>     - Makes `lastMessageTs` public in `Cline.ts` for better accessibility.
>   - **UI Changes**:
>     - Updates `CommandExecution` component in `ChatRow.tsx` and `CommandExecution.tsx` to use `message.ts` for `executionId`.
>     - Adjusts `ChatView.tsx` to handle auto-approval and button state more effectively.
> 
> <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=RooVetGit%2FRoo-Code&utm_source=github&utm_medium=referral)<sup> for 6e86438d83b2f5ef0743a38bcb83115a8ef8b094. You can [customize](https://app.ellipsis.dev/RooVetGit/settings/summaries) this summary. It will automatically update as commits are pushed.</sup>


<!-- ELLIPSIS_HIDDEN -->


Diff:
diff --git a/.changeset/dull-flowers-trade.md b/.changeset/dull-flowers-trade.md
new file mode 100644
index 00000000000..7cbe6d9f8ed
--- /dev/null
+++ b/.changeset/dull-flowers-trade.md
@@ -0,0 +1,5 @@
+---
+"roo-cline": patch
+---
+
+Fix auto-approvals
diff --git a/src/core/Cline.ts b/src/core/Cline.ts
index 4779275ff34..eeb17df08d9 100644
--- a/src/core/Cline.ts
+++ b/src/core/Cline.ts
@@ -161,7 +161,7 @@ export class Cline extends EventEmitter<ClineEvents> {
 	private askResponse?: ClineAskResponse
 	private askResponseText?: string
 	private askResponseImages?: string[]
-	private lastMessageTs?: number
+	public lastMessageTs?: number
 
 	// Not private since it needs to be accessible by tools.
 	consecutiveMistakeCount: number = 0
diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts
index b65ce34b19a..a911da98ec3 100644
--- a/src/core/tools/attemptCompletionTool.ts
+++ b/src/core/tools/attemptCompletionTool.ts
@@ -76,13 +76,13 @@ export async function attemptCompletionTool(
 				}
 
 				// Complete command message.
-				const executionId = Date.now().toString()
-				const didApprove = await askApproval("command", command, { id: executionId })
+				const didApprove = await askApproval("command", command)
 
 				if (!didApprove) {
 					return
 				}
 
+				const executionId = cline.lastMessageTs?.toString() ?? Date.now().toString()
 				const options: ExecuteCommandOptions = { executionId, command }
 				const [userRejected, execCommandResult] = await executeCommand(cline, options)
 
@@ -108,7 +108,7 @@ export async function attemptCompletionTool(
 				}
 
 				// tell the provider to remove the current subtask and resume the previous task in the stack
-				await cline.providerRef.deref()?.finishSubTask(lastMessage?.text ?? "")
+				await cline.providerRef.deref()?.finishSubTask(result)
 				return
 			}
 
diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts
index 4bd303904f7..97ec0e6ffca 100644
--- a/src/core/tools/executeCommandTool.ts
+++ b/src/core/tools/executeCommandTool.ts
@@ -48,14 +48,14 @@ export async function executeCommandTool(
 
 			cline.consecutiveMistakeCount = 0
 
-			const executionId = Date.now().toString()
 			command = unescapeHtmlEntities(command) // Unescape HTML entities.
-			const didApprove = await askApproval("command", command, { id: executionId })
+			const didApprove = await askApproval("command", command)
 
 			if (!didApprove) {
 				return
 			}
 
+			const executionId = cline.lastMessageTs?.toString() ?? Date.now().toString()
 			const clineProvider = await cline.providerRef.deref()
 			const clineProviderState = await clineProvider?.getState()
 			const { terminalOutputLineLimit = 500, terminalShellIntegrationDisabled = false } = clineProviderState ?? {}
diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts
index 70376cf9483..386c983cc88 100644
--- a/src/exports/roo-code.d.ts
+++ b/src/exports/roo-code.d.ts
@@ -353,7 +353,6 @@ type ClineMessage = {
 		| undefined
 	progressStatus?:
 		| {
-				id?: string | undefined
 				icon?: string | undefined
 				text?: string | undefined
 		  }
@@ -429,7 +428,6 @@ type RooCodeEvents = {
 					| undefined
 				progressStatus?:
 					| {
-							id?: string | undefined
 							icon?: string | undefined
 							text?: string | undefined
 					  }
diff --git a/src/exports/types.ts b/src/exports/types.ts
index c2da70391f2..330f9c8c21e 100644
--- a/src/exports/types.ts
+++ b/src/exports/types.ts
@@ -358,7 +358,6 @@ type ClineMessage = {
 		| undefined
 	progressStatus?:
 		| {
-				id?: string | undefined
 				icon?: string | undefined
 				text?: string | undefined
 		  }
@@ -438,7 +437,6 @@ type RooCodeEvents = {
 					| undefined
 				progressStatus?:
 					| {
-							id?: string | undefined
 							icon?: string | undefined
 							text?: string | undefined
 					  }
diff --git a/src/schemas/index.ts b/src/schemas/index.ts
index ff8c01f0cff..c6e34fe3955 100644
--- a/src/schemas/index.ts
+++ b/src/schemas/index.ts
@@ -817,7 +817,6 @@ export type ClineSay = z.infer<typeof clineSaySchema>
  */
 
 export const toolProgressStatusSchema = z.object({
-	id: z.string().optional(),
 	icon: z.string().optional(),
 	text: z.string().optional(),
 })
diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx
index 714c47ee5b1..efc720ba76e 100644
--- a/webview-ui/src/components/chat/ChatRow.tsx
+++ b/webview-ui/src/components/chat/ChatRow.tsx
@@ -985,7 +985,7 @@ export const ChatRowContent = ({
 								{icon}
 								{title}
 							</div>
-							<CommandExecution executionId={message.progressStatus?.id} text={message.text} />
+							<CommandExecution executionId={message.ts.toString()} text={message.text} />
 						</>
 					)
 				case "use_mcp_server":
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx
index 4dabee8ade9..c2fb21353bb 100644
--- a/webview-ui/src/components/chat/ChatView.tsx
+++ b/webview-ui/src/components/chat/ChatView.tsx
@@ -735,7 +735,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 
 	const isAutoApproved = useCallback(
 		(message: ClineMessage | undefined) => {
-			if (!autoApprovalEnabled || !message || message.type !== "ask") return false
+			if (!autoApprovalEnabled || !message || message.type !== "ask") {
+				return false
+			}
 
 			if (message.ask === "browser_action_launch") {
 				return alwaysAllowBrowser
@@ -749,7 +751,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 				return alwaysAllowExecute && isAllowedCommand(message)
 			}
 
-			// For read/write operations, check if it's outside workspace and if we have permission for that
+			// For read/write operations, check if it's outside workspace and if
+			// we have permission for that.
 			if (message.ask === "tool") {
 				let tool: any = {}
 
@@ -1114,13 +1117,26 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 		}
 
 		const autoApprove = async () => {
-			if (isAutoApproved(lastMessage)) {
+			if (lastMessage?.ask && isAutoApproved(lastMessage)) {
+				// Note that `isAutoApproved` can only return true if
+				// lastMessage is an ask of type "browser_action_launch",
+				// "use_mcp_server", "command", or "tool".
+
 				// Add delay for write operations.
-				if (lastMessage?.ask === "tool" && isWriteToolAction(lastMessage)) {
+				if (lastMessage.ask === "tool" && isWriteToolAction(lastMessage)) {
 					await new Promise((resolve) => setTimeout(resolve, writeDelayMs))
 				}
 
-				handlePrimaryButtonClick()
+				vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
+
+				// This is copied from `handlePrimaryButtonClick`, which we used
+				// to call from `autoApprove`. I'm not sure how many of these
+				// things are actually needed.
+				setInputValue("")
+				setSelectedImages([])
+				setTextAreaDisabled(true)
+				setClineAsk(undefined)
+				setEnableButtons(false)
 			}
 		}
 		autoApprove()
diff --git a/webview-ui/src/components/chat/CommandExecution.tsx b/webview-ui/src/components/chat/CommandExecution.tsx
index 827e7119489..005484c36db 100644
--- a/webview-ui/src/components/chat/CommandExecution.tsx
+++ b/webview-ui/src/components/chat/CommandExecution.tsx
@@ -14,7 +14,7 @@ import { cn } from "@src/lib/utils"
 import { Button } from "@src/components/ui"
 
 interface CommandExecutionProps {
-	executionId?: string
+	executionId: string
 	text?: string
 }
 
@@ -36,10 +36,6 @@ export const CommandExecution = ({ executionId, text }: CommandExecutionProps) =
 
 	const onMessage = useCallback(
 		(event: MessageEvent) => {
-			if (!executionId) {
-				return
-			}
-
 			const message: ExtensionMessage = event.data
 
 			if (message.type === "commandExecutionStatus") {


Reviews:
user: login: cte
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-04T03:57:58Z
user: login: mrubens
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-04T04:01:12Z
user: login: daniel-lxs
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T04:07:04Z
user: login: cte
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-04T04:08:09Z
user: login: mrubens
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-04T04:08:51Z
user: login: cte
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-04T04:13:07Z
user: login: ellipsis-dev[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T06:20:25Z
user: login: cte
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-04T06:54:57Z

Review comments:
diff_hunk: @@ -108,7 +108,7 @@ export async function attemptCompletionTool(
 				}
 
 				// tell the provider to remove the current subtask and resume the previous task in the stack
-				await cline.providerRef.deref()?.finishSubTask(lastMessage?.text ?? "")
+				await cline.providerRef.deref()?.finishSubTask(result)
path: src/core/tools/attemptCompletionTool.ts
user: login: mrubens
body: This seems to make a lot more sense - hopefully not missing any obvious reason to use the lastMessage text
created_at: 2025-05-04T04:01:12Z
author_association: COLLABORATOR
diff_hunk: @@ -108,7 +108,7 @@ export async function attemptCompletionTool(
 				}
 
 				// tell the provider to remove the current subtask and resume the previous task in the stack
-				await cline.providerRef.deref()?.finishSubTask(lastMessage?.text ?? "")
+				await cline.providerRef.deref()?.finishSubTask(result)
path: src/core/tools/attemptCompletionTool.ts
user: login: daniel-lxs
body: This is only the `result` parameter from the `attempt_completion` tool right?

created_at: 2025-05-04T04:07:04Z
author_association: MEMBER
diff_hunk: @@ -108,7 +108,7 @@ export async function attemptCompletionTool(
 				}
 
 				// tell the provider to remove the current subtask and resume the previous task in the stack
-				await cline.providerRef.deref()?.finishSubTask(lastMessage?.text ?? "")
+				await cline.providerRef.deref()?.finishSubTask(result)
path: src/core/tools/attemptCompletionTool.ts
user: login: mrubens
body: Yes exactly. Seems like that's what we want to return to the parent task.
created_at: 2025-05-04T04:08:51Z
author_association: COLLABORATOR
diff_hunk: @@ -150,23 +149,9 @@ export async function executeCommand(
 	const clineProvider = await cline.providerRef.deref()
 
 	const callbacks: RooTerminalCallbacks = {
-		onLine: async (output: string, process: RooTerminalProcess) => {
+		onLine: async (output: string) => {
 			const status: CommandExecutionStatus = { executionId, status: "output", output }
 			clineProvider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) })
-
-			if (runInBackground) {
-				return
-			}
-
-			try {
-				const { response, text, images } = await cline.ask("command_output", "")
path: src/core/tools/executeCommandTool.ts
user: login: cte
body: To expand on this - we changed the way "Continue while command executes" works. It's triggered directly by the webview with a `terminalOperation` event rather than the `ask` mechanism. One thing that might have been lost is the ability to provide text to the model when you trigger background execution, but I can fix that.
created_at: 2025-05-04T04:13:06Z
author_association: COLLABORATOR
diff_hunk: @@ -441,6 +439,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
 		(text?: string, images?: string[]) => {
 			const trimmedInput = text?.trim()
 
+			console.log(`handlePrimaryButtonClick -> clineAsk=${clineAsk}`, text)
path: webview-ui/src/components/chat/ChatView.tsx
user: login: ellipsis-dev[bot]
body: Remove or replace `console.log` debug statements in production code. Debug logs like this may clutter the console and should be managed with proper logging or removed.
```suggestion

```

created_at: 2025-05-04T06:20:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -184,7 +184,9 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
 			await provider.postStateToWebview()
 			break
 		case "askResponse":
-			provider.getCurrentCline()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
+			const instance = provider.getCurrentCline()
+			console.log("askResponse ->", message, !!instance)
path: src/core/webview/webviewMessageHandler.ts
user: login: ellipsis-dev[bot]
body: Consider replacing the direct `console.log` with a proper logging utility (e.g. `provider.log`) or remove it before production to avoid exposing internal state.
```suggestion
			provider.log(`askResponse ->`, message, !!instance)
```

<sup>This comment was generated because it violated a code review rule: [mrule_OR1S8PRRHcvbdFib](https://app.ellipsis.dev/RooVetGit/code-review/rules?id=mrule_OR1S8PRRHcvbdFib).</sup>
created_at: 2025-05-04T06:20:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -76,13 +76,13 @@ export async function attemptCompletionTool(
 				}
 
 				// Complete command message.
-				const executionId = Date.now().toString()
-				const didApprove = await askApproval("command", command, { id: executionId })
+				const didApprove = await askApproval("command", command)
 
 				if (!didApprove) {
 					return
 				}
 
+				const executionId = cline.lastMessageTs?.toString() ?? Date.now().toString()
path: src/core/tools/attemptCompletionTool.ts
user: login: cte
body: This is a better value for `executionId` since it will get passed as `message.ts` for the `command` message. I was noticing issues with existing method for passing an `executionId`.
created_at: 2025-05-04T06:54:57Z
author_association: COLLABORATOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-04T03:43:02Z
body: ###  🦋  Changeset detected

Latest commit: 6e86438d83b2f5ef0743a38bcb83115a8ef8b094

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name      | Type  |
| --------- | ----- |
| roo-cline | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/RooVetGit/Roo-Code/new/fix_subtasks_with_commands?filename=.changeset/fuzzy-donkeys-give.md&value=---%0A%22roo-cline%22%3A%20patch%0A---%0A%0AFix%20issues%20with%20subtasks%20attempting%20completion%20along%20with%20commands%0A)


author_association: NONE
user: login: KJ7LNW
created_at: 2025-05-10T20:26:53Z
body: @cte I think you added `{ id: executionId }` as part of execa refactor in 1d7569b14c , but this PR removed that:

```diff
-  const didApprove = await askApproval("command", command, { id: executionId })
+  const didApprove = await askApproval("command", command)
```

I do not know all the internal refactor workings of execa, and it looks like you talked about `executionId` above, but I wanted to ask: 

is there any possibility that this could cause a regression in your changes?
author_association: CONTRIBUTOR