Skip to content

Keep the real error when user code throws, instead of reporting an escaped break - #3005

Open
nohwnd wants to merge 1 commit into
mainfrom
nohwnd-beforeall-names
Open

Keep the real error when user code throws, instead of reporting an escaped break#3005
nohwnd wants to merge 1 commit into
mainfrom
nohwnd-beforeall-names

Conversation

@nohwnd

@nohwnd nohwnd commented Aug 23, 2026

Copy link
Copy Markdown
Member

Found while looking at #2646. A BeforeAll that throws reports the wrong error, and the real one is discarded:

Where the throw is Reported
top-level BeforeAll A 'break' or 'continue' statement with a label that does not match any enclosing loop escaped from your code…
Describe-level BeforeAll same
inside an It correct

So someone whose setup failed because a database was down is told their code has a misspelled loop label, and what it actually threw is gone.

Cause

The #2669 guard in Invoke-ScriptBlock:

$flowControlEscaped = $MoveBetweenScopes   # true for user code
try {
    do { ...invoke... } while ($false)
    $flowControlEscaped = $false           # skipped when user code throws
}
finally {
    if ($flowControlEscaped) { throw (New-EscapedFlowControlErrorRecord) }
}

A genuine throw skips the reset, so the finally throws the guard's error record on top of the real exception, and in PowerShell the exception from a finally wins.

Fix

Clear the flag on the error path too. A labelled break/continue escape is not catchable, which is the whole premise of the guard, so the new catch only ever runs for real errors and the guard still works.

Verified all four cases:

top      -> TOP LEVEL ERROR
inner    -> INNER ERROR
intest   -> IN TEST ERROR
label    -> A 'break' or 'continue' statement with a label that does not match...

Two regression tests added next to the existing #2669 ones.

Note

This is not #2646 itself. That one is about the output omitting the test names when a BeforeAll fails, and the result object already contains every test marked Failed — so it is a rendering fix at a different layer, and it is still open.

tst excluding the parallel file is at 2910 passed / 12 failed, identical to main's baseline on the same machine. The P phase is clean apart from the Invoke-InRunspacePool failure that #3004 fixes.

🤖

…caped break

A BeforeAll that threw reported "A 'break' or 'continue' statement with a label
that does not match any enclosing loop escaped from your code", whatever it
actually threw. Someone whose setup failed because a database was down was told
their code has a misspelled loop label, and the real error was gone.

The #2669 guard in Invoke-ScriptBlock sets $flowControlEscaped up front for user
code and only clears it on the normal return path. A genuine throw skips that line,
so the finally threw the flow control error record on top of the real exception,
and in PowerShell the exception from a finally wins. Clearing the flag on the error
path as well keeps the real error. A labelled break or continue escape is not
catchable, so the new catch only ever runs for real errors and the guard still
does its job.

Verified all four: a throw at the top level, a throw in a Describe-level BeforeAll,
a throw in an It, and a real unmatched-label break, which still reports the guard.

🤖
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant