pwsh 7.6 crashes with ObjectDisposedException in PipelineProcessor.Stop when Ctrl+C arrives as a native command ends · Issue #28299 · PowerShell/PowerShell · GitHub
What happens. A Ctrl+C delivered while a native command is ending sometimes ends the whole shell instead of stopping the pipeline. PowerShell prints its unhandled-exception banner and exits:
An error has occurred that was not properly handled. Additional information is shown below. The PowerShell process will exit.
Unhandled exception. System.ObjectDisposedException: The CancellationTokenSource has been disposed.
at System.Threading.CancellationTokenSource.Cancel()
at System.Management.Automation.Internal.PipelineProcessor.Stop()
at System.Management.Automation.Runspaces.PipelineStopper.Stop()
at System.Management.Automation.Runspaces.LocalPipeline.StopHelper()
at System.Management.Automation.Runspaces.PipelineBase.CoreStop(Boolean syncCall)
at Microsoft.PowerShell.Executor.Cancel()
at Microsoft.PowerShell.Executor.CancelCurrentExecutor()
at Microsoft.PowerShell.ConsoleHost.HandleBreak()
at System.Threading.Thread.StartCallback()
How it was reproduced. pwsh runs interactively in a pseudoconsole (CreatePseudoConsole). It runs node -e "require('http').createServer((q,s)=>s.end('ok')).listen(0,()=>console.log('started'))", and after started appears the host writes 0x03 to the pseudoconsole's input, which with ENABLE_PROCESSED_INPUT is delivered as CTRL_C_EVENT. Node exits on the signal and pwsh normally returns to its prompt. With the host restricted to two CPUs and other shells running alongside, pwsh occasionally crashed instead: once in fifteen attempts in one run, and less often in others. The timing points to the Ctrl+C arriving while the pipeline is completing.
Likely cause. In src/System.Management.Automation/engine/pipeline.cs, PipelineProcessor.Stop() is documented as callable asynchronously and keeps a copy of _commands "in case Dispose() is called", but it then calls _pipelineStopTokenSource.Cancel() unguarded. Dispose(bool) disposes _pipelineStopTokenSource. When the break handler's Stop() runs after the pipeline's Dispose(), Cancel() throws, and nothing on the break-handler thread catches it. The token source and the Cancel() call came with #24620 (PipelineStopToken), in March 2025.
Expected. A Ctrl+C that arrives as a pipeline ends is ignored or stops the next prompt's input, and the shell keeps running.
Possible fix. Make the cancel in Stop() safe against a concurrent Dispose(), for example by synchronising the two around _disposed or by catching ObjectDisposedException there, or have Dispose() leave the token source to be released once no stop can race it.
Thanks for the bug report and sorry I added this feature without considering this scenario. I've opened #28300 which catches ObjectDisposedException as well as another scenario that could cause a similar crash. It'll be up to the WG to review when they get to it.
We're hitting the same crash on Linux through a different path: no Ctrl+C and no native command. It happens during runspace close at process exit, when a module has background runspaces that are still running.
Environment
PowerShell 7.6.9, AlmaLinux 10.2 x64
Non-interactive: pwsh -NoProfile -ExecutionPolicy Bypass -File script.ps1, started by a job scheduler (no TTY)
What happens
The script imports dbatools, which starts two background runspaces (dbatools-logging and dbatools-maintenance). Each runs a loop with Start-Sleep. The script then runs a SQL query, completes successfully and returns. During the shutdown that follows, pwsh crashes with exit code 134 (SIGABRT). It's intermittent: about once a day across many scheduled runs (one job alone runs every 10 minutes). The script's own work always completes before the crash.
Stack trace
Process terminated.
The CancellationTokenSource has been disposed.
at System.Environment.FailFast(System.String, System.Exception)
at Microsoft.PowerShell.UnmanagedPSEntry.Start(System.String[], Int32)
at Microsoft.PowerShell.ManagedPSEntry.Main(System.String[])
System.ObjectDisposedException: The CancellationTokenSource has been disposed.
at System.Threading.CancellationTokenSource.Cancel()
at System.Management.Automation.Internal.PipelineProcessor.Stop()
at System.Management.Automation.Runspaces.PipelineStopper.Stop()
at System.Management.Automation.Runspaces.LocalPipeline.StopHelper()
at System.Management.Automation.Runspaces.PipelineBase.CoreStop(Boolean syncCall)
at System.Management.Automation.Runspaces.RunspaceBase.StopPipelines()
at System.Management.Automation.Runspaces.LocalRunspace.DoCloseHelper()
at System.Management.Automation.Runspaces.RunspaceBase.CoreClose(Boolean syncCall)
at System.Management.Automation.Runspaces.LocalRunspace.Dispose(Boolean disposing)
at System.Management.Automation.Runspaces.LocalRunspace.DoCloseHelper()
at System.Management.Automation.Runspaces.RunspaceBase.CoreClose(Boolean syncCall)
at System.Management.Automation.Runspaces.LocalRunspace.Close()
at Microsoft.PowerShell.ConsoleHost.DoRunspaceLoop(...)
at Microsoft.PowerShell.ConsoleHost.Run(CommandLineParameterParser cpp, Boolean isPrestartWarned)
at Microsoft.PowerShell.ConsoleHost.Start(String bannerText, String helpText, Boolean issProvidedExternally)
at Microsoft.PowerShell.UnmanagedPSEntry.Start(String[] args, Int32 argc)
So the path is: closing the primary runspace, then disposing another local runspace that is still running, then StopPipelines(), then the same unguarded _pipelineStopTokenSource.Cancel() in PipelineProcessor.Stop(). Unlike the Ctrl+C case, the exception is caught in UnmanagedPSEntry.Start and turned into Environment.FailFast.
Dump analysis (runtime dump via DOTNET_DbgEnableMiniDump)
clrstack -all shows the main thread in the FailFast stack above. One other pipeline thread (LocalPipeline.InvokeThreadProc) is running a script block nested several try/catch/finally levels deep and is sitting in StartSleepCommand.Sleep. That fits a dbatools background-runspace loop: a short-lived inner pipeline is created and disposed on each loop iteration, so it can be disposed just as the outer stop reaches it.
Workaround
Stopping the module's background runspaces explicitly before the script exits removes the trigger:
It would be great if #28300 also covers this path (stop during runspace close/dispose at process exit), since this has nothing to do with Ctrl+C or native commands. I'm happy to collect more diagnostics if that would help. I can't share the dumps themselves because they contain credentials.
Ilya (@corelevel) as the proposed PR catches ObjectDisposedException I believe it should also cover your use case because it's essentially hitting the same problem where the pipeline was disposed and thus the stop token but then stop signal is still processed after it was disposed.
Activity
jborean93 commented on Nov 2, 2026
Thanks for the bug report and sorry I added this feature without considering this scenario. I've opened #28300 which catches ObjectDisposedException as well as another scenario that could cause a similar crash. It'll be up to the WG to review when they get to it.
corelevel commented on Nov 4, 2026
We're hitting the same crash on Linux through a different path: no Ctrl+C and no native command. It happens during runspace close at process exit, when a module has background runspaces that are still running.
Environment
What happens
The script imports dbatools, which starts two background runspaces (dbatools-logging and dbatools-maintenance). Each runs a loop with Start-Sleep. The script then runs a SQL query, completes successfully and returns. During the shutdown that follows, pwsh crashes with exit code 134 (SIGABRT). It's intermittent: about once a day across many scheduled runs (one job alone runs every 10 minutes). The script's own work always completes before the crash.
Stack trace
So the path is: closing the primary runspace, then disposing another local runspace that is still running, then StopPipelines(), then the same unguarded _pipelineStopTokenSource.Cancel() in PipelineProcessor.Stop(). Unlike the Ctrl+C case, the exception is caught in UnmanagedPSEntry.Start and turned into Environment.FailFast.
Dump analysis (runtime dump via DOTNET_DbgEnableMiniDump)
clrstack -all shows the main thread in the FailFast stack above. One other pipeline thread (LocalPipeline.InvokeThreadProc) is running a script block nested several try/catch/finally levels deep and is sitting in StartSleepCommand.Sleep. That fits a dbatools background-runspace loop: a short-lived inner pipeline is created and disposed on each loop iteration, so it can be disposed just as the outer stop reaches it.
Workaround
Stopping the module's background runspaces explicitly before the script exits removes the trigger:
It would be great if #28300 also covers this path (stop during runspace close/dispose at process exit), since this has nothing to do with Ctrl+C or native commands. I'm happy to collect more diagnostics if that would help. I can't share the dumps themselves because they contain credentials.
jborean93 commented on Nov 4, 2026
Ilya (@corelevel) as the proposed PR catches ObjectDisposedException I believe it should also cover your use case because it's essentially hitting the same problem where the pipeline was disposed and thus the stop token but then stop signal is still processed after it was disposed.