Skip to content

Python: Report repeated native exit codes in persistent PowerShell sessions - #9658

Open
Aditya Kumar (Aditya-XR) wants to merge 1 commit into
microsoft:mainfrom
Aditya-XR:fix/pwsh-repeated-exit-code
Open

Aditya Kumar (Aditya-XR) wants to merge 1 commit into
microsoft:mainfrom
Aditya-XR:fix/pwsh-repeated-exit-code

Conversation

@Aditya-XR

Copy link
Copy Markdown
Contributor

Motivation & Context

In a persistent PowerShell session, LocalShellTool reports exit_code=0 when a native command fails with the same code as the previous native command. ShellSession._build_script decides whether the command ran a native process by checking whether $LASTEXITCODE changed ($LASTEXITCODE -ne $__af_last). A repeated code doesn't change the value, and the $? fallback can't catch it, because $? reflects Invoke-Expression, which succeeded. An agent that reruns a failing test command is told that the second run passed. This affects anyone using the tool on Windows, where PowerShell is the default shell, and anyone running pwsh in persistent mode elsewhere.

Description & Review Guide

  • What are the major changes?

    One line in the PowerShell branch of _build_script: the check compares references instead of values.

    -"   if ($LASTEXITCODE -ne $__af_last) { $__af_rc = $LASTEXITCODE }"
    +"   if (-not [object]::ReferenceEquals($LASTEXITCODE, $__af_last)) { $__af_rc = $LASTEXITCODE }"

    PowerShell stores a newly boxed int in $LASTEXITCODE every time a native process exits, even when the code is the same, so the variable holds a different object exactly when this command ran a native process (or assigned the variable itself). $__af_last keeps the reference taken before the command. Like Python: reset $LASTEXITCODE per command in persistent PowerShell sessions #8206, this never writes $LASTEXITCODE, so the user's own commands can still read the previous code. The comments above the check now explain why it compares references.

    There is also a regression test, test_persistent_powershell_reports_repeated_native_exit_code. It runs a Python process that exits with 3 three times and expects 3 each time, then checks that a cmdlet-only command reports 0 and that $LASTEXITCODE still reads back 3. Without the fix it fails with AssertionError: attempt 2 reported rc 0. Like test_persistent_powershell_returns_formatted_object_output, it is gated on PowerShell being installed rather than on sys.platform == "win32", and it uses sys.executable as the native command, so it should also run in the ubuntu-latest CI job.

  • What is the impact of these changes?

    Repeated native failures now report their own exit code. For the repro in the linked issue (Windows 11, PowerShell 7.6.6):

    exit_code=1    <- python -c 'import sys; sys.exit(1)'
    exit_code=1    <- python -c 'import sys; sys.exit(1)'
    exit_code=1    <- python -c 'import sys; sys.exit(1)'
    exit_code=128  <- git rev-parse --git-dir
    exit_code=128  <- git rev-parse --git-dir
    

    The other exit-code paths are unchanged. I compared before and after on PowerShell 7.6.6 and Windows PowerShell 5.1 for cmdlet-only commands in a fresh session (where $LASTEXITCODE is $null) and after a native failure, reading $LASTEXITCODE back, a cmdlet error after a native failure, a native command followed by a cmdlet, several native commands in one command (the last one decides), $global:LASTEXITCODE = 9, throw, and table-formatted output. The only differences were the repeated-code cases, which now report the right code. Bash sessions and stateless mode are untouched.

  • What do you want reviewers to focus on?

    Whether relying on reference identity is acceptable. I checked it on PowerShell 7.6.6 and Windows PowerShell 5.1 for exit codes 0, 1, 2, 3, 127, 255 and -1, and both store a new boxed int on every native exit. The alternatives I considered either write $LASTEXITCODE before the command, which Python: reset $LASTEXITCODE per command in persistent PowerShell sessions #8206 avoided so the user's commands can still read it, or need a different way to run the command than Invoke-Expression.

Related Issue

Fixes #9657

This is a one-line correction to the exit-code check added in #8206, so I opened it together with the issue.

AI Assistance

  • No material AI assistance was used.
  • This is an AI-assisted contribution. I reviewed, understood, and verified all submitted content and accept responsibility for it.

AI assistance details: I used AI coding assistants (Claude Code and Antigravity) to help find the bug and to draft the fix, the test, the linked issue and this description. I did the final review myself: I read and understood every change, checked the repro and test results, and made the final decisions on what to submit.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR links to an agreed issue with no competing open PR, or the Related Issue section documents a trivial-change or repository-automation exception.
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

…ssions

A persistent PowerShell session reported exit code 0 when a native command
failed with the same code as the previous one. The exit-code check compared
$LASTEXITCODE by value, which does not change when the code repeats, and $?
reflects Invoke-Expression rather than the native command. Compare by
reference instead: PowerShell stores a newly boxed int on every native exit,
so the variable holds a different object exactly when the command ran a
native process. $LASTEXITCODE is still never written.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Oct 23, 2026

This branch was successfully deployed

1 active deployment
github-app-auth — 2c2bfbb4 Deployed Oct 23, 2026 by Aditya-XR via team_check #7352
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: Persistent PowerShell LocalShellTool reports exit_code 0 when a native command fails with the same code twice

2 participants