@@ -12,7 +12,12 @@ import { Task } from "../task/Task"
1212import { ToolUse , ToolResponse } from "../../shared/tools"
1313import { formatResponse } from "../prompts/responses"
1414import { unescapeHtmlEntities } from "../../utils/text-normalization"
15- import { ExitCodeDetails , RooTerminalCallbacks , RooTerminalProcess } from "../../integrations/terminal/types"
15+ import {
16+ ExitCodeDetails ,
17+ RooTerminalCallbacks ,
18+ RooTerminalProcess ,
19+ ShellIntegrationErrorDetails ,
20+ } from "../../integrations/terminal/types"
1621import { TerminalRegistry } from "../../integrations/terminal/TerminalRegistry"
1722import { Terminal } from "../../integrations/terminal/Terminal"
1823import { OutputInterceptor } from "../../integrations/terminal/OutputInterceptor"
@@ -21,7 +26,18 @@ import { t } from "../../i18n"
2126import { getTaskDirectoryPath } from "../../utils/storage"
2227import { BaseTool , ToolCallbacks } from "./BaseTool"
2328
24- class ShellIntegrationError extends Error { }
29+ export class ShellIntegrationError extends Error {
30+ constructor (
31+ message : string ,
32+ public readonly commandSubmitted : boolean ,
33+ ) {
34+ super ( message )
35+ }
36+ }
37+
38+ export function canRetryShellIntegrationError ( error : unknown ) : error is ShellIntegrationError {
39+ return error instanceof ShellIntegrationError && ! error . commandSubmitted
40+ }
2541
2642interface ExecuteCommandParams {
2743 command : string
@@ -116,14 +132,14 @@ export class ExecuteCommandTool extends BaseTool<"execute_command"> {
116132
117133 pushToolResult ( result )
118134 } catch ( error : unknown ) {
119- const status : CommandExecutionStatus = { executionId, status : "fallback" }
120- provider ?. postMessageToWebview ( { type : "commandExecutionStatus" , text : JSON . stringify ( status ) } )
121- await task . say ( "shell_integration_warning" )
122-
123135 // Invalidate pending ask from first execution to prevent race condition
124136 task . supersedePendingAsk ( )
125137
126- if ( error instanceof ShellIntegrationError ) {
138+ if ( canRetryShellIntegrationError ( error ) ) {
139+ // Silent retry via execa — shell startup race, command was not submitted.
140+ const status : CommandExecutionStatus = { executionId, status : "fallback" }
141+ provider ?. postMessageToWebview ( { type : "commandExecutionStatus" , text : JSON . stringify ( status ) } )
142+
127143 const [ rejected , result ] = await executeCommandInTerminal ( task , {
128144 ...options ,
129145 terminalShellIntegrationDisabled : true ,
@@ -135,7 +151,16 @@ export class ExecuteCommandTool extends BaseTool<"execute_command"> {
135151
136152 pushToolResult ( result )
137153 } else {
138- pushToolResult ( `Command failed to execute in terminal due to a shell integration error.` )
154+ // Command was submitted but shell integration lost track of it — show warning.
155+ await task . say ( "shell_integration_warning" )
156+
157+ if ( error instanceof ShellIntegrationError ) {
158+ pushToolResult (
159+ "Command was submitted in the VS Code terminal, but shell integration did not report its output or completion status. Do not run the command again automatically." ,
160+ )
161+ } else {
162+ pushToolResult ( `Command failed to execute in terminal due to a shell integration error.` )
163+ }
139164 }
140165 }
141166
@@ -196,12 +221,20 @@ export async function executeCommandInTerminal(
196221 let result : string = ""
197222 let persistedResult : PersistedCommandOutput | undefined
198223 let exitDetails : ExitCodeDetails | undefined
199- let shellIntegrationError : string | undefined
224+ let shellIntegrationError : ShellIntegrationError | undefined
200225 let hasAskedForCommandOutput = false
201226
202- const terminalProvider = terminalShellIntegrationDisabled ? "execa" : "vscode"
227+ const isCmdExeFallback = ! terminalShellIntegrationDisabled && Terminal . isActiveShellCmdExe ( )
228+ const terminalProvider = terminalShellIntegrationDisabled || isCmdExeFallback ? "execa" : "vscode"
203229 const provider = await task . providerRef . deref ( )
204230
231+ // cmd.exe can't use shell integration — tell the webview to expand the output
232+ // panel immediately (same effect as the retry-fallback path).
233+ if ( isCmdExeFallback ) {
234+ const status : CommandExecutionStatus = { executionId, status : "fallback" }
235+ provider ?. postMessageToWebview ( { type : "commandExecutionStatus" , text : JSON . stringify ( status ) } )
236+ }
237+
205238 // Get global storage path for persisted output artifacts
206239 const globalStoragePath = provider ?. context ?. globalStorageUri ?. fsPath
207240 let interceptor : OutputInterceptor | undefined
@@ -360,9 +393,9 @@ export async function executeCommandInTerminal(
360393 }
361394
362395 if ( terminalProvider === "vscode" ) {
363- callbacks . onNoShellIntegration = async ( error : string ) => {
396+ callbacks . onNoShellIntegration = async ( details : ShellIntegrationErrorDetails ) => {
364397 TelemetryService . instance . captureShellIntegrationError ( task . taskId )
365- shellIntegrationError = error
398+ shellIntegrationError = new ShellIntegrationError ( details . message , details . commandSubmitted )
366399 }
367400 }
368401
@@ -442,7 +475,7 @@ export async function executeCommandInTerminal(
442475 }
443476
444477 if ( shellIntegrationError ) {
445- throw new ShellIntegrationError ( shellIntegrationError )
478+ throw shellIntegrationError
446479 }
447480
448481 // Wait for a short delay to ensure all messages are sent to the webview.
0 commit comments