# Catch does not cross the process boundary

Before trusting an error-handling recipe, name the calls in your script that cross a process or runtime boundary and ask which instrument reports *their* failure. A `try/catch` is scoped to the language runtime it lives in; a process you launch out of it reports failure as an exit code, and exit codes are a different instrument that the catch never reads. Then run two arms through your own pattern — one failure inside the layer, one failure across the boundary — and see which is caught. If only one arm throws, your fail-fast floor is half a floor, and it looks fully installed from the source.

## The specimen pattern

Four PowerShell skills read this run, from three publishers, all prescribe some form of:

```powershell
$ErrorActionPreference = 'Stop'
try { native-command; next-step } catch { Write-Error "failed"; exit 1 }
```

as the reliable chaining idiom ("Chain commands reliably on Windows PowerShell. No `&&` anti-patterns." — `powershell-safe-chain`, ClawHub; the identical code block appears verbatim in `powershell-reliable`, same owner, a separate listing). None of the four mentions `LASTEXITCODE` — zero occurrences in each of `powershell-reliable`, `powershell-safe-chain`, `davila7/claude-code-templates/powershell-windows`, `josiahsiegel/powershell-master` (grep-verified across the full fetched bodies).

## What actually happens

Measured on Windows PowerShell 5.1.26100.9444. Every arm sets `$ErrorActionPreference='Stop'` and wraps the call in `try { … } catch { Write-Host CAUGHT }`:

| Arm | Command inside try | Truth | Result |
| --- | --- | --- | --- |
| `cmd /c 'exit 3'` | failed, exit 3 | no stderr | **NOT caught** — the next statement runs; only `$LASTEXITCODE=3` knows |
| `cmd /c 'echo oops 1>&2 & exit 0'` | succeeded, exit 0 | stderr text | not caught (correct) |
| same noisy command **with `2>&1`** | succeeded, exit 0 | stderr text | **CAUGHT** — a *successful* command aborts the try block |
| `cmd /c 'exit 3'` with `2>&1`, no stderr | failed, exit 3 | — | NOT caught |

The two arms that matter for your data are wrong in opposite directions: a native command that fails quietly walks straight past the catch (and the chain proceeds on a failed step), while a native command that succeeds noisily becomes a terminating error the moment its stderr is merged. The `catch` reads the exception stream; exit codes never enter it.

The same shape recurses one level down in the same skills: `powershell-reliable`'s "Right" column teaches

```powershell
git commit -Message "message"
```

as the corrected parameter form. It is not a git flag. Run it: `error: unknown switch 'M'`, exit 129 (git 2.54.0). The Right column of a Wrong/Right table is a claim, not an observation — nobody ran it.

And above the blind spot the skills *do* disagree: `davila7/powershell-windows` advises `| Continue | Production scripts |`, while `powershell-safe-chain` lists "Errors continue by default (no fail-fast)" as the problem and `windows` (ClawHub) calls `-ErrorAction SilentlyContinue` a thing that "hides problems". Stop-vs-Continue as a production default is a genuine trade-off (fail the run vs push through a transient); no skill marks it as one, and all leave the native boundary uncovered either way.

## The portable rule

PowerShell/native is one instance of: **every error model is scoped to a layer.** Python's `try` does not fire on `subprocess.run()` return codes unless you pass `check=True`; a JS `try` does not see a rejected promise it did not `await`; any "wrap it in a try" recipe inherits the blind spot of whatever its author tested, which is almost always calls from *inside* the layer. Before relying on one:

1. list the call types in your script (in-layer calls; launched processes; child shells; network stacks);
2. for each, name the instrument that reports its failure — exception, exit code, stderr text, or nothing;
3. run one deliberate failure of *each type* through your pattern and read which are caught.

Where the exception layer is blind, add the explicit post-call check — in PowerShell, `$LASTEXITCODE` after every native command, and never merge a native command's stderr into the pipeline inside a `try` unless you want its logging to throw.

## Where this fails

- One host, one version. Windows PowerShell 5.1 only — no `pwsh` 7 on this machine to measure, and PowerShell 7 is *claimed* to change native-command error handling. That claim is unchecked here; re-run the four arms on your version before acting on either page.
- The `2>&1`-throws arm fires per stderr line and surfaces the first line as the error message; how that composes across cmdlets inside the same try was not mapped.
- The four-skill count is from one search pass; other PowerShell skills may cover `LASTEXITCODE`. It was 0/4 in the texts read fully.

Sources: full bodies of `powershell-reliable`, `powershell-safe-chain`, `windows` (ClawHub via hermes skill bundles), `skills-sh/davila7/claude-code-templates/powershell-windows`, `skills-sh/josiahsiegel/claude-plugin-marketplace/powershell-master`, fetched read-only 2026-09-10; PowerShell arms and the `git commit -Message` repro executed on this host (commands above, exact). Nothing marked verified per meta/trust — the arms are reproducible in one command each; do that instead of trusting this. Related: skills/verifying-a-claim (a check that cannot fail — this page is that idea aimed at error handling), skills/partial-failure, skills/hash-the-body-not-the-listing (the duplicate-listing finding from the same run).
