Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

.NET: Python: [Bug]: Persistent PowerShell LocalShellTool returns empty stdout for commands that output objects

Abierto
#9,042 1 comentario 0 reacciones 1 asignado Ver en GitHub

Los mantenedores suelen responder en 1 día

@eavanvalkenburg ya está trabajando en esto.

Desde el 4/10/2026.

  • #9043 de @Aditya-XR — abierto

Evaluación

Este issue todavía no se ha evaluado.

Descripción

.NET python reproduced skills
Description

LocalShellTool in its default mode="persistent" returns an empty stdout for any PowerShell command whose output gets formatted as a table or list. That includes Get-Location / pwd, anything piped through Select-Object, Get-Process, and [pscustomobject]. The command succeeds with exit_code=0, but the agent gets nothing back. Once an object has been written, plain strings that come after it in the same command are lost too. mode="stateless" returns all of this output.

This happens on Windows, where PowerShell is the default shell, and with pwsh on Linux, so it isn't Windows-specific.

Cause: ShellSession._build_script runs the command as & { try { Invoke-Expression $__af_cmd; ... } finally { [Console]::WriteLine('<sentinel>_' + $__af_rc) } } and leaves formatting to the host. The host's Out-Default only flushes table/list formatting when the outer pipeline ends, which is after the script block has returned. By then finally has already written the sentinel straight to [Console]. So the formatted output lands after the sentinel in the stdout buffer, and _run_locked throws it away, since it keeps only the bytes before the sentinel and then clears the buffer. Plain strings survive only because Out-Default writes them immediately.

The .NET ShellSession already handles this. BuildScript in dotnet/src/Microsoft.Agents.AI.Tools.Shell/ShellSession.cs formats the output inside the try, with the comment: "Without this, pwsh defers Out-Default formatting until the script block returns and the sentinel races ahead of the user's output in the byte stream."

Expected: persistent mode returns the same stdout as stateless mode.

Code Sample
import asyncio
import re
import shutil

from agent_framework.tools import LocalShellTool

POWERSHELL = shutil.which("pwsh") or shutil.which("powershell")
SHELLS = {
    # the argv LocalShellTool uses by default on Windows
    "persistent": [POWERSHELL, "-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "-"],
    "stateless": [POWERSHELL],
}
COMMANDS = [
    "Get-Location",
    "Get-Item $PSHOME | Select-Object Name",
    "[pscustomobject]@{ Name = 'an object' }; Write-Output 'a string after it'",
    "Write-Output 'just a string'",
]


async def main() -> None:
    for mode, shell in SHELLS.items():
        async with LocalShellTool(mode=mode, shell=shell) as tool:
            for command in COMMANDS:
                result = await tool.run(command)
                stdout = re.sub(r"\x1b\[[0-9;?]*[A-Za-z]", "", result.stdout).strip()  # drop terminal escape codes
                print(f"{mode:<10} exit_code={result.exit_code} stdout={stdout!r}")
                print(f"{'':<10} ^ {command}")


asyncio.run(main())

Output on main with Linux (Debian 12, PowerShell 7.6.6):

persistent exit_code=0 stdout=''
           ^ Get-Location
persistent exit_code=0 stdout=''
           ^ Get-Item $PSHOME | Select-Object Name
persistent exit_code=0 stdout=''
           ^ [pscustomobject]@{ Name = 'an object' }; Write-Output 'a string after it'
persistent exit_code=0 stdout='just a string'
           ^ Write-Output 'just a string'
stateless  exit_code=0 stdout='Path\n----\n/tmp'
           ^ Get-Location
stateless  exit_code=0 stdout='Name\n----\n7'
           ^ Get-Item $PSHOME | Select-Object Name
stateless  exit_code=0 stdout='Name\n----\nan object\na string after it'
           ^ [pscustomobject]@{ Name = 'an object' }; Write-Output 'a string after it'
stateless  exit_code=0 stdout='just a string'
           ^ Write-Output 'just a string'

I get the same result on Windows 11 with PowerShell 7.6.6 and with Windows PowerShell 5.1, and the same with the default LocalShellTool() there, without passing shell=.

Error Messages / Stack Traces

None. The empty stdout comes back with exit_code=0.

Package Versions

agent-framework-tools: 1.0.0b261002, agent-framework-core: 1.20.0 (source checkout of main at e9857bd75)

Python Version

Python 3.13

Additional Context
  • The persistent PowerShell tests in python/packages/tools/tests/test_local_shell_tool.py only check plain strings and exit codes, so none of them covers output PowerShell formats as a table. The re-anchor test, for example, reads (Get-Location).Path, which returns a string and so doesn't hit this. Plain Get-Location would.
  • Fix: format the output inside the try, before finally writes the sentinel: Invoke-Expression $__af_cmd | Out-Default;. Unlike a per-object Out-String, this keeps PowerShell's normal formatting (one table per command). Exit codes for native failures, cmdlet errors, throw, and $LASTEXITCODE readback stay the same. I'll open a PR with this and a regression test.
Lenguaje dominante
Python
Estrellas
13.9k
Forks
2.4k
Merge medio
1 d 14 h
PR fusionados (30 d)
420

Preparar el entorno

Abrir en Codespaces

Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de microsoft/agent-framework

Todos los issues de microsoft/agent-framework

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.