Skip to content

Shell Integration Warning when using MiMo: Root Cause Analysis and Fix #752

Description

@myk1yt

Shell Integration Warning when using MiMo: Root Cause Analysis and Fix

Environment

  • AI Model: MiMo 2.5 Pro (Xiaomi) — long Chain-of-Thought reasoning causes extended wait times between terminal commands, and tendency to maintain long sessions makes these bugs especially prominent
  • Zoo Code: v3.65.100202 (zoocodeorganization.zoo-code)
  • OS: Windows 11, PowerShell 5.1
  • Target File: dist/extension.js (15,442,677 bytes)
  • Date: 2026-06-28

Summary of Bugs Found

#BugSeverityUser ExperienceRoot Cause Function
1Dead terminal reuse infinite loop🔴 CriticalCommand execution permanently impossible (10+ consecutive failures)getOrCreateTerminal()
2Missing shellPath fallback🔴 CriticalPowerShell cmdlets don't execute on WindowsLn constructor
3execa provider cmd.exe fallback🔴 Criticalgit commit and other commands with quotes failxit.run()
4Intermittent command failures (3 causes)🟠 HighSame command alternates between success/failure (~35% failure rate)getOrCreateTerminal(), catch block, Olr.execute()
5Missing customCwd type validation🟠 HighTerminal state permanently corrupted, all commands failCFi()

Bug 1: Dead Terminal Reuse Infinite Loop

Symptom

When executing commands via execute_command, if the terminal process is dead (user closed the panel, shell integration initialization failed, etc.), Zoo Code enters an infinite loop reusing the same dead terminal.

Command attempt → Terminal process already dead → ShellIntegrationError
→ Error handler retries with same taskId → getOrCreateTerminal returns same dead terminal
→ ShellIntegrationError again → infinite loop

Actual error message:

Command failed to execute in terminal due to a shell integration error.

VS Code UI warning:

Your command is being executed without VSCode terminal shell integration.

Real reproduction timeline (23 attempts in 16 minutes, 15 failures — 35% success rate):

[05:22:47] python analyze_script.py → ✅ success
[05:23:07] powershell Get-ChildItem → ❌ "shell integration error"
[05:23:25] python analyze_script.py → ❌ "shell integration error"
[05:23:32] python analyze_script.py → ❌ "shell integration error"
... (10 consecutive failures) ...
[05:24:49] python analyze_script.py → ✅ success (new terminal assigned)
[05:25:11] python -c "..." → ❌ "shell integration error"
[05:25:19] python -c "..." → ✅ success
[05:25:26] python -c "..." → ❌ "shell integration error"

Key observation: success/failure occurs regardless of command content. echo "test" succeeds, then python -c "print('test')" fails immediately after.

Root Cause Code (extension.js, getOrCreateTerminal(), offset ~14,921,404)

Call chain:

Olr.execute() [execute_command tool handler]
└─ try { CFi(r, w) } [terminal command execution]
├─ Fh.getOrCreateTerminal(c, t.taskId, N) [find or create terminal]
└─ Y.runCommand(r, Q) [execute command in terminal]
└─ onNoShellIntegration → new Yq() [ShellIntegrationError thrown]
└─ catch(E)
└─ FNl(E) → true → CFi(r, {...w, terminalShellIntegrationDisabled:!0})
⚠️ Same dead terminal returned → infinite loop

getOrCreateTerminal()'s find() searches by taskId + provider + reuseKey + cwd, but dead terminals also satisfy all these conditions. There's no isClosed() check, so dead terminals are returned as-is.

// ❌ Original code — getOrCreateTerminal() (offset ~14,921,404)staticasyncgetOrCreateTerminal(e,r,a="vscode"){leto=this.getAllTerminals(),s=a==="vscode" ? Ln.getReuseKey() : a,n;returnr&&(n=o.find(l=>{if(l.busy||l.taskId!==r||l.provider!==a||l.reuseKey!==s)return!1;letc=l.getCurrentWorkingDirectory();returnc ? jo(Rq.Uri.file(e).fsPath,c) : !1})),n||(n=o.find(l=>{if(l.busy||l.provider!==a||l.reuseKey!==s)return!1;letc=l.getCurrentWorkingDirectory();returnc ? jo(Rq.Uri.file(e).fsPath,c) : !1})),n||(n=this.createTerminal(e,a)),n.taskId=r,n}

The error handler also doesn't create a new terminal on retry:

// ❌ Original code — catch block (offset ~15,077,884)catch(E){if(r.supersedePendingAsk(),FNl(E)){letm={executionId: v,status: "fallback"};x?.postMessageToWebview({type: "commandExecutionStatus",text: JSON.stringify(m)});let[g,y]=awaitCFi(r,{...w,terminalShellIntegrationDisabled: !0});// ⚠️ No forceNewTerminal option → getOrCreateTerminal returns same dead terminalg&&(r.didRejectTool=!0),c(y)}}

Fix (before/after)

Fix 1/5 — Add 4th parameter to getOrCreateTerminal():

// ✅ After fix — getOrCreateTerminal()staticasyncgetOrCreateTerminal(e,r,a="vscode",__fn=!1){leto=this.getAllTerminals(),s=a==="vscode" ? Ln.getReuseKey() : a,n;return__fn||(// ← When __fn is true, skip find() calls entirelyr&&(n=o.find(l=>{if(l.busy||l.isClosed()||l.taskId!==r||l.provider!==a||l.reuseKey!==s)return!1;// ^^^^^^^^^^ Exclude dead terminalsletc=l.getCurrentWorkingDirectory();returnc ? jo(Rq.Uri.file(e).fsPath,c) : !1})),n||(n=o.find(l=>{if(l.busy||l.isClosed()||l.provider!==a||l.reuseKey!==s)return!1;// ^^^^^^^^^^ Exclude dead terminalsletc=l.getCurrentWorkingDirectory();returnc ? jo(Rq.Uri.file(e).fsPath,c) : !1}))),n||(n=this.createTerminal(e,a)),// ← __fn=true always reaches here → new terminaln.taskId=r,n}

Fix 2/5 — Add forceNewTerminal option to CFi() function (offset ~15,078,569):

// ❌ OriginalasyncfunctionCFi(t,{executionId:e,command:r,customCwd:a,terminalShellIntegrationDisabled:o=!0,commandExecutionTimeout:s=0,agentTimeout:n=0}){// ✅ After fixasyncfunctionCFi(t,{executionId:e,command:r,customCwd:a,terminalShellIntegrationDisabled:o=!0,commandExecutionTimeout:s=0,agentTimeout:n=0,forceNewTerminal:__fn=!1}){

Fix 3/5 — Forward __fn to getOrCreateTerminal call (offset ~15,080,813):

// ❌ OriginalletY=awaitFh.getOrCreateTerminal(c,t.taskId,N);// ✅ After fixletY=awaitFh.getOrCreateTerminal(c,t.taskId,N,__fn);

Fix 4/5 — Enable forceNewTerminal on error handler retry (offset ~15,078,012):

// ❌ Originallet[g,y]=awaitCFi(r,{...w,terminalShellIntegrationDisabled: !0});// ✅ After fixlet[g,y]=awaitCFi(r,{...w,terminalShellIntegrationDisabled: !0,forceNewTerminal: !0});

Fix 5/5 — Include default value in initial options object (offset ~15,077,775):

// ❌ Original
agentTimeout: I};// ✅ After fix
agentTimeout: I,forceNewTerminal: !1};

Bug 2: Missing shellPath Fallback

Symptom

On Windows, when the VS Code terminal profile is set to cmd.exe or no profile exists, Zoo Code doesn't set shellPath when creating a terminal, falling back to VS Code's default (cmd.exe). Since cmd.exe doesn't support shell integration, all commands fail.

Actual error messages:

Command: Test-Path "README.md"
Result: 'Test-Path' is not recognized as an internal or external command, operable program or batch file.
Command: if (Test-Path "README.md") { (Get-Item "README.md").Length }
Result: ")" was unexpected at this time.

Root Cause Code (extension.js, Ln constructor, offset ~13,190,853)

// ❌ Original code — Ln class constructorvarLn=classtextendsmD{terminal;cmdCounter=0;activeShellExecution;constructor(e,r,a){super("vscode",e,a,t.getReuseKey());leto=t.getEnv(),s=newTf.ThemeIcon("rocket");if(r)this.terminal=r;else{letn={cwd: a,name: "Zoo Code",iconPath: s,env: o},l=t.getProfileShell();l?.shellPath&&(// ← When shellPath is missing, this entire block is skippedn.shellPath=l.shellPath,l.shellArgs&&(n.shellArgs=l.shellArgs),console.info(`[Terminal] Creating terminal with profile "${t.getTerminalProfile()}" -> ${l.shellPath}`),l.env&&(n.env={...l.env, ...o})),this.terminal=Tf.window.createTerminal(n)// ⚠️ createTerminal called without n.shellPath set// → VS Code uses cmd.exe (Windows default)// → No shell integration support → all commands fail}}

Fix (before/after)

// ✅ After fix — Ln class constructorl?.shellPath// ← Changed to ternary operator
? (// ← Profile has shellPath (preserve existing behavior)n.shellPath=l.shellPath,l.shellArgs&&(n.shellArgs=l.shellArgs),console.info(`[Terminal] Creating terminal with profile "${t.getTerminalProfile()}" -> ${l.shellPath}`),l.env&&(n.env={...l.env, ...o}))
: (// ← No shellPath: explicit fallbackn.shellPath=process.platform==="win32"
? (process.env.SystemRoot||"C:\\Windows")+"\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
: process.env.SHELL||"/bin/bash",console.info(`[Terminal] No profile shellPath found; using fallback: ${n.shellPath}`)),this.terminal=Tf.window.createTerminal(n)

Bug 3: execa Provider cmd.exe Fallback

Symptom

When the terminal provider is "execa" instead of "vscode", commands like git commit trigger the shell integration warning. Zoo Code AI detects: "The terminal appears to be running cmd.exe".

Root Cause Code (extension.js, xit.run(), offset ~14,916,022)

// ❌ Original code — execa shell path setting
shell: mD.getExecaShellPath()||!0// When getExecaShellPath() is undefined → ||!0 → true// execa with shell:true → Windows COMSPEC → cmd.exe

Fix (before/after)

// ❌ Original
shell: mD.getExecaShellPath()||!0// ✅ After fix
shell: mD.getExecaShellPath()||(process.platform==="win32"
? (process.env.SystemRoot||"C:\\Windows")+"\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
: process.env.SHELL||"/bin/bash")

Bug 4: Intermittent Command Failures (3 Causes)

Symptom

After fixing the above 3 bugs, command execution still intermittently fails (~35% failure rate). Same command alternates between success and failure regardless of content.

Cause 1: Missing isClosed() check in getOrCreateTerminal()

// ❌ Original — find() without isClosed() checkn=o.find(l=>{if(l.busy||l.taskId!==r|| ...)return!1;// ⚠️ No l.isClosed() check!})// ✅ After fixn=o.find(l=>{if(l.busy||l.isClosed()||l.taskId!==r|| ...)return!1;// ^^^^^^^^^^ Added})

Cause 2: Missing retry for non-Yq errors

// ❌ Original — catch blockcatch(E){if(FNl(E)){awaitCFi(r,{...w,forceNewTerminal:!0});}else{// non-Yq error → warning only, no retry!awaitr.say("shell_integration_warning");}}// ✅ After fixcatch(E){if(FNl(E)){awaitCFi(r,{...w,forceNewTerminal:!0});}elseif(!(EinstanceofYq)){// non-Yq error also retries with new terminalawaitCFi(r,{...w,terminalShellIntegrationDisabled:!0,forceNewTerminal:!0});}else{awaitr.say("shell_integration_warning");}}

Cause 3: terminalShellIntegrationDisabled default value problem

// ❌ Original — default true forces execa provider{terminalShellIntegrationDisabled:N=!0}=R??{}// ✅ After fix — default false uses vscode provider{terminalShellIntegrationDisabled:N=!1}=R??{}

Bug 5: Missing customCwd Type Validation

Symptom

When customCwd is passed as an Object instead of string, TypeError occurs. Terminal state becomes permanently corrupted — all subsequent commands fail. Cannot recover even by reloading VS Code.

Stack Trace

TypeError: The "path" argument must be of type string. Received an instance of Object
at Object.isAbsolute (node:path:482:5)
at CFi (extension.js:5086:5100)
at Olr.execute (extension.js:5086:4351)

Fix (before/after)

// ❌ Originala ? cV.isAbsolute(a) ? c=a : c=cV.resolve(a) : ...
// ✅ After fixa&&typeofa==="string" ? cV.isAbsolute(a) ? c=a : c=cV.resolve(a) : ...

Impact Analysis: MiMo Model Relevance

These bugs affect all AI models. However, MiMo 2.5 Pro's characteristics make them more frequent and more visible:

  1. Long Chain-of-Thought: Extended wait times between terminal commands → higher shell integration timeout chance
  2. Iterative tool calls: Many steps → frequent terminal use → dead terminal accumulation
  3. Long sessions: 30+ minute sessions → failure rate spikes

GPT-4, Claude, Gemini, and other models experience the same bugs under the same conditions. The root cause is in Zoo Code's code, not the AI model.


Verification Results

Before fix (16 minutes): 23 attempts, 15 failures → 35% success rate

After fix: 10 consecutive commands tested, 9/10 success (90%). The 1 failure is a PowerShell 5.x && syntax limitation, not a Zoo Code bug. Zoo Code related errors: 0.


Official Fix Suggestions (TypeScript)

1. Terminal reuse state check

staticasyncgetOrCreateTerminal(cwd: string,taskId: string,provider: string="vscode",forceNew: boolean=false){constterminals=this.getAllTerminals();letterminal: TerminalWrapper|undefined;if(!forceNew){terminal=terminals.find(t=>{if(t.busy||t.isClosed()||t.taskId!==taskId||t.provider!==provider)returnfalse;// ... cwd matching logic});}if(!terminal)terminal=this.createTerminal(cwd,provider);terminal.taskId=taskId;returnterminal;}

2. shellPath fallback chain

constprofile=Terminal.getProfileShell();if(profile?.shellPath){options.shellPath=profile.shellPath;}else{options.shellPath=process.platform==="win32"
? `${process.env.SystemRoot||"C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
: process.env.SHELL||"/bin/bash";}

3. execa provider shell path fallback

shell: ExecaTerminal.getExecaShellPath()||(process.platform==="win32"
? `${process.env.SystemRoot||"C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
: process.env.SHELL||"/bin/bash")

4. Error handler non-Yq error retry

catch(error){task.supersedePendingAsk();if(isShellIntegrationError(error)&&!error.commandSubmitted){// Retry with new terminalconst[rejected,result]=awaitexecuteCommandInTerminal(task,{
...options,terminalShellIntegrationDisabled: true,forceNewTerminal: true,});resolve(result);}elseif(!(errorinstanceofShellIntegrationError)){// Non-Yq errors also retryconst[rejected,result]=awaitexecuteCommandInTerminal(task,{
...options,terminalShellIntegrationDisabled: true,forceNewTerminal: true,});resolve(result);}else{awaittask.say("shell_integration_warning");resolve("Command failed...");}}

5. customCwd type validation

constcwd=customCwd&&typeofcustomCwd==="string"
? (path.isAbsolute(customCwd) ? customCwd : path.resolve(customCwd))
: defaultCwd;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions