Repository navigation
Description
Version: PowerShell 7.6.8 on Windows 11 (x64).
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.
Activity
jborean93 commentedon Nov 2, 2026
Thanks for the bug report and sorry I added this feature without considering this scenario. I've opened #28300 which catches
ObjectDisposedExceptionas 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 commentedon 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
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-logginganddbatools-maintenance). Each runs a loop withStart-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()inPipelineProcessor.Stop(). Unlike the Ctrl+C case, the exception is caught inUnmanagedPSEntry.Startand turned intoEnvironment.FailFast.Dump analysis (runtime dump via
DOTNET_DbgEnableMiniDump)clrstack -allshows the main thread in the FailFast stack above. One other pipeline thread (LocalPipeline.InvokeThreadProc) is running a script block nested severaltry/catch/finallylevels deep and is sitting inStartSleepCommand.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 commentedon Nov 4, 2026
Ilya (@corelevel) as the proposed PR catches
ObjectDisposedExceptionI 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.