-
Notifications
You must be signed in to change notification settings - Fork 0
PR 025 P2: Release child before hardening error normalization #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: repair/pr010-hardening-failure-settlement
Are you sure you want to change the base?
Changes from 1 commit
b243bd5
f5fe331
19a8d56
879d2c4
e5cdf4c
7a99af6
556d642
36f8ae0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -828,20 +828,40 @@ export function invokeAgentProcess( | |
| if (invocation.signal !== null) { | ||
| removeAbortListener(invocation.signal, onAbort); | ||
| } | ||
| // Normalised here, before the asynchronous release, so the reason this | ||
| // exchange rejects with is already fixed and cannot itself be lost to a | ||
| // later hostile read. | ||
| const hardeningFailure = | ||
| error instanceof Error | ||
| ? error | ||
| : new Error('Process dispatch hardening failed', { cause: error }); | ||
| // Started before the thrown value is examined at all. Classifying it is | ||
| // not a neutral read: `instanceof` consults the value's own prototype | ||
| // chain, and a value engineered to refuse that makes the classification | ||
| // itself throw. Ordering the release first is what keeps such a fault | ||
| // from costing an already-created child the one bounded release attempt | ||
| // it is owed, and from displacing the mandatory hardening failure as the | ||
| // reason this exchange rejects with. Nothing below decides anything this | ||
| // call depends on. | ||
| const release = releaseUnprotectedChild(child, platform, invocation.graceMs); | ||
| // Total. The ordinary case keeps the original Error as the caller-visible | ||
| // reason; a value that is not an Error — or that faults while being | ||
| // classified — yields the same stable hardening failure instead, with the | ||
| // original value retained as `cause`. Retaining it is safe because a | ||
| // `cause` is only stored, never read. Neither branch can escape, so the | ||
| // reason is fixed here, before the release settles, and cannot itself be | ||
| // lost to a later hostile read. | ||
| let hardeningFailure: Error; | ||
| try { | ||
| hardeningFailure = | ||
| error instanceof Error | ||
| ? error | ||
| : new Error('Process dispatch hardening failed', { cause: error }); | ||
| } catch { | ||
| hardeningFailure = new Error('Process dispatch hardening failed', { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the hardening failure is a Proxy whose Useful? React with 👍 / 👎. |
||
| cause: error, | ||
| }); | ||
| } | ||
| // `releaseUnprotectedChild` runs every step and never rejects, and the | ||
| // rejection is scheduled on *both* settlement paths of the chain anyway, | ||
| // so neither a termination failure nor a cleanup step that throws on a | ||
| // poisoned `stdout`/`stderr` value can leave this exchange pending or | ||
| // leave an internal rejection unhandled. The mandatory hardening failure | ||
| // stays the externally visible reason on every one of those paths. | ||
| void releaseUnprotectedChild(child, platform, invocation.graceMs).then( | ||
| void release.then( | ||
| () => { | ||
| reject(hardeningFailure); | ||
| }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When hardening throws an actual
Errorthat is also reachable from a hostile child accessor, this call can synchronously mutate that error before theinstanceofcheck runs; for example, a configurablechild.pidgetter can change the thrown error's prototype whenterminate()reads the PID. The subsequent classification then returns false or throws, causing the caller to receive the genericProcess dispatch hardening failederror rather than the originalError, regressing the promised ordinary-error identity preservation. Normalize inside a construct that guarantees release in all cases, or otherwise snapshot the classification before release can invoke hostile accessors.Useful? React with 👍 / 👎.