Description
On Windows, LocalShellTool(mode="persistent") reports the exit code of an earlier command for any later command that only runs cmdlets.
ShellSession._build_script derives the return code from $LASTEXITCODE:
" Invoke-Expression $__af_cmd;"
" if ($LASTEXITCODE -ne $null) { $__af_rc = $LASTEXITCODE }"
" elseif (-not $?) { $__af_rc = 1 }"
$LASTEXITCODE is a session-wide automatic variable that PowerShell only updates when a native (external) executable exits. Cmdlets leave it untouched. Since the session is long-lived, once any command in it has run an external process, $LASTEXITCODE stays set, and every subsequent cmdlet-only command takes the if branch and reports that stale value instead of 0.
The blast radius is that the tool tells the agent a command failed when it succeeded, for the whole rest of the session, until another external command happens to reset the variable.
Stateless mode is unaffected — each command is a fresh process and the rc comes from the process exit code.
Code Sample
import asyncio
from agent_framework_tools.shell._resolve import resolve_shell
from agent_framework_tools.shell._session import ShellSession
async def main():
async with ShellSession(resolve_shell(None, interactive=True)) as s:
for cmd in [
"Write-Output 'cmdlet only'",
"cmd /c exit 3",
"Write-Output 'cmdlet only, after a failing native command'",
"Write-Output 'cmdlet only again'",
]:
r = await s.run(cmd, timeout=20)
print(f"exit_code={r.exit_code:<4} cmd={cmd}")
asyncio.run(main())
Output I get on Windows 11 with Windows PowerShell 5.1:
exit_code=0 cmd=Write-Output 'cmdlet only'
exit_code=3 cmd=cmd /c exit 3
exit_code=3 cmd=Write-Output 'cmdlet only, after a failing native command'
exit_code=3 cmd=Write-Output 'cmdlet only again'
The last two commands succeeded and printed their output, but are reported as exit code 3.
Expected: 0, 3, 0, 0.
Error Messages / Stack Traces
No exception — the wrong value is returned silently in ShellResult.exit_code.
Package Versions
agent-framework-tools: 1.0.0b260730 (from a source checkout of main at 992d445)
Python Version
Python 3.12.9
Additional Context
This is Windows-only, and the CI matrix (.github/workflows/python-*.yml) is ubuntu-latest everywhere, so the existing PowerShell tests in python/packages/tools/tests/test_local_shell_tool.py — the ones guarded by @pytest.mark.skipif(sys.platform != "win32", ...) — never actually execute anywhere.
The .NET port has the same lines in dotnet/src/Microsoft.Agents.AI.Tools.Shell/ShellSession.cs (around line 592), so it very likely has the same behaviour. I have not built or run the .NET side, so I am reporting that as an observation rather than a verified claim.
Description
On Windows,
LocalShellTool(mode="persistent")reports the exit code of an earlier command for any later command that only runs cmdlets.ShellSession._build_scriptderives the return code from$LASTEXITCODE:$LASTEXITCODEis a session-wide automatic variable that PowerShell only updates when a native (external) executable exits. Cmdlets leave it untouched. Since the session is long-lived, once any command in it has run an external process,$LASTEXITCODEstays set, and every subsequent cmdlet-only command takes theifbranch and reports that stale value instead of 0.The blast radius is that the tool tells the agent a command failed when it succeeded, for the whole rest of the session, until another external command happens to reset the variable.
Stateless mode is unaffected — each command is a fresh process and the rc comes from the process exit code.
Code Sample
Output I get on Windows 11 with Windows PowerShell 5.1:
The last two commands succeeded and printed their output, but are reported as exit code 3.
Expected: 0, 3, 0, 0.
Error Messages / Stack Traces
No exception — the wrong value is returned silently in
ShellResult.exit_code.Package Versions
agent-framework-tools: 1.0.0b260730 (from a source checkout of
mainat 992d445)Python Version
Python 3.12.9
Additional Context
This is Windows-only, and the CI matrix (
.github/workflows/python-*.yml) isubuntu-latesteverywhere, so the existing PowerShell tests inpython/packages/tools/tests/test_local_shell_tool.py— the ones guarded by@pytest.mark.skipif(sys.platform != "win32", ...)— never actually execute anywhere.The .NET port has the same lines in
dotnet/src/Microsoft.Agents.AI.Tools.Shell/ShellSession.cs(around line 592), so it very likely has the same behaviour. I have not built or run the .NET side, so I am reporting that as an observation rather than a verified claim.