.agents/skills/project-knowledge/references/pwsh.md
Register-EngineEvent (e.g. PowerShell.OnIdle) -Action handlers, verified pwsh 7 (2026-07):
-MessageData is accepted but arrives as $null in the action (unlike Register-ObjectEvent,
where it works)..GetNewClosure() bindings are lost when the scriptblock is created inside a module function -
captured variables resolve to $null when the action fires.$global: variable
(see $global:_ompStreamingState in src/shell/scripts/omp.ps1).PowerShell.OnIdle itself every ~300-450ms while waiting for input (only
when the input buffer is empty) and pumps ALL queued subscriber actions via a nested pipeline.
InvokePrompt() is designed for OnIdle subscribers.Register-ObjectEvent DataAdded on a collection
appended by a background runspace) can re-enter RunspaceBase.Pulse() and crash the host
with InvalidPipelineStateException under rapid prompt cycles. Consume records only on the engine
thread: sync waiter plus OnIdle drain.Register-ObjectEvent actions fire during a busy-wait depends on where the wait runs
(top-level script vs busy pipeline thread) - it is not a given either way. Never share
a consume-cursor between an event action and a synchronous waiter - give the waiter a private
cursor (records stay in the PSDataCollection) and make the action idempotent.[powershell]::Create() pipeline thread runs - pipeline threads are
foreground threads. A reader runspace blocked in ReadByte() on a child's stdout deadlocks
exit when that child exits only on stdin EOF (issue #7643).OnRemove only runs on Remove-Module, never on normal exit - it cannot be a
daemon's teardown path. Use a Register-EngineEvent PowerShell.Exiting handler that writes
quit, closes the child's stdin (the guaranteed EOF signal), and kills after a short
WaitForExit. State reaches the action via $global: (see above).cmd /c exit); any omp spawn from pwsh costs ~100-130ms wall
regardless of exe speed. When touching omp.ps1 perf, count process spawns per Enter first -
the spawn-per-prompt architecture dominates, script/module cmdlet overhead is negligible
(<0.1ms; module vs plain script is a non-issue).& call operator vs the Process API is only ~10-17ms faster - CreateProcess dominates. Under a
stock CP437/1252 console, & mangles UTF-8 output (U+E0B6 becomes U+03B5); dropping the Process
API requires [Console]::OutputEncoding = UTF8 once at init.Start-Sleep -Milliseconds 1 is a ~15.6ms tick - sleep-polling
loops are ~16x slower than intended. Prefer a ManualResetEventSlim signaled by the reader.New-Event -SourceIdentifier PowerShell.OnIdle runs the real OnIdle action deterministically
(engines never idle mid-script). Beware the action's side effects short-circuiting later prompt
calls in the harness.& (Get-Module oh-my-posh-core) { $script:X = $true }.Start-Process pwsh -File test.ps1 + WaitForExit(timeout);
findstr x with redirected stdio is a good stand-in daemon.