Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Chiusa
#9,042 1 commento 0 reazioni 1 assegnatario Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@eavanvalkenburg ci sta già lavorando.

Dal 4/10/2026.

  • #9043 di @Aditya-XR — aperta

Valutazione

Questa issue non è ancora stata valutata.

Descrizione

.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.
Lingua principale
Python
Stelle
13.9k
Fork
2.4k
Merge medio
1g 16h
PR unite (30g)
432

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di microsoft/agent-framework

Tutte le issue di microsoft/agent-framework

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.