Skip to content

fix(bindings): keep the error chain when a binding error reaches TypeScript (ALIEN-1249) - #910

Merged
ItamarZand88 merged 2 commits into
mainfrom
itamar/alien-1249-the-node-bindings-drop-a-sandbox-errors-cause-so-a-refusal
Oct 6, 2026
Merged

ItamarZand88 merged 2 commits into
mainfrom
itamar/alien-1249-the-node-bindings-drop-a-sandbox-errors-cause-so-a-refusal

Conversation

@ItamarZand88

@ItamarZand88 ItamarZand88 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A binding error that reaches TypeScript now keeps the error that caused it. Before this, a sandbox refusal said "the cause carries which side refused", but the JS error arrived with source and cause both undefined.

When a binding call fails in a Node app:

  1. The provider returns an AlienError whose source holds the chain, for example the cloud's own HTTP response under SANDBOX_COMMAND_FAILED.
  2. The addon serializes the error into a JSON envelope, which now includes source.
  3. unwrapNapiError passes source to the TypeScript AlienError, which rebuilds the chain.

This PR changes the envelope from code-and-message-only to code, message and cause chain.

What was broken

ErrorEnvelope in alien-bindings-node had no source field, and unwrapNapiError never passed one, so both sides dropped the chain.

What I did

  • Added source to the envelope. It is the error's existing chain, serialized the way AlienError serializes everywhere else. The TypeScript AlienError schema is generated from that same struct.
  • unwrapNapiError passes envelope.source through, and the TypeScript constructor rebuilds the nested sources.
  • The AlienError constructor in @alienplatform/core builds the source before super and passes that same object as cause. Before this, cause held the raw serialized object, so code that follows Error.cause saw a plain object.
  • Each layer keeps its own internal flag, so toExternal() still hides internal layers one by one.
  • Woz: nothing to remove.
  • Limitation accepted: the chain now reaches app code in full. If an app logs the error, the cloud's response text and resource URL appear in its logs. The app already knows those identifiers from its binding config, and the response is the cloud's answer to the app's own call.

How I tested

  • New Rust test a_wrapped_error_reaches_typescript_with_the_error_that_caused_it. It failed before the change (env["source"] was Null) and passes after. It also checks the inner layer's internal and retryable.
  • New TypeScript test for unwrapNapiError with a two-level chain. It failed before the change (expected undefined to be 'HTTP_RESPONSE_ERROR') and passes after. It also checks err.cause === err.source.
  • cargo test -p alien-bindings-node --lib: 33 passed. packages/bindings unit tests: 73 passed. packages/core tests: 178 passed. tsc --noEmit is clean. The full bindings suite against the real addon: 133 passed.
  • Not yet seen on a real cloud: an Azure run that reaches the sandbox check with this build is in progress, and I'll add its result here.

Safe because only the in-process error gains a field. The SDK and command runtimes still forward only error.message, which is unchanged (packages/sdk/src/worker-runtime/event-loop.ts:34, packages/commands/src/receiver.ts:540). Proven by the real-addon suite above, which still passes unchanged.

@greptile-apps

greptile-apps Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Error handling now preserves the chain of what caused each error.

The PR appears safe to merge; the previously reported cause-chain inconsistency is fixed.

Summary

The PR carries the native binding error’s serialized source chain into TypeScript. The follow-up change makes Error.cause reference the same reconstructed AlienError as source, addressing the previous finding.

  • Adds a Rust envelope test and a TypeScript chain test.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Rust AlienError source chain] --> B[napi JSON envelope]
  B --> C[unwrapNapiError]
  C --> D[TypeScript AlienError]
  D --> E["cause === source"]
Loading

Reviews (2) · Last reviewed commit: "fix(core): make Error.cause the rebuilt ..."

Comment thread packages/bindings/src/errors.ts
@ItamarZand88
ItamarZand88 merged commit 23933ab into main Oct 6, 2026
24 checks passed
@ItamarZand88
ItamarZand88 deleted the itamar/alien-1249-the-node-bindings-drop-a-sandbox-errors-cause-so-a-refusal branch October 6, 2026 15:04
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