Skip to content

fix(sdk-build): build embedded Python/Node SDKs with panic=unwind - #3783

Merged
codeshaunted merged 2 commits into
canaryfrom
avery/bridge-sdk-unwind-profile
Jun 17, 2026
Merged

codeshaunted merged 2 commits into
canaryfrom
avery/bridge-sdk-unwind-profile

Conversation

@codeshaunted

@codeshaunted codeshaunted commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The Python and Node SDKs load the engine as a native library inside the user's own process (baml_py.abi3.so via PyO3, the .node addon via napi). The release profile sets panic = "abort" (a binary-size win from #3110). So a raw Rust panic anywhere in the engine — e.g. a stdlib slice bounds check — calls process::abort() and SIGABRTs the user's interpreter: their whole server dies, exit 134, uncatchable even by except BaseException.

The bridges already have machinery to contain such a panic — a catch_unwind → baml.panics.SdkPanic boundary in bridge_cffi, plus tokio's per-task isolation in the in-engine HTTP server — but all of it only runs when the stack can unwind, which abort forbids. It was effectively dead code in shipped wheels/addons.

Fix

Add release-bridge-python / release-bridge-node profiles (inherits = "release", panic = "unwind") and point the maturin and napi release builds at them. panic is a whole-artifact setting (can't be per-crate, can't mix strategies in one binary), so the embedded SDKs get their own profile selected at build time.

The CLI and wasm stay on release/abort: each owns its own process and is size-gated, so an abort there is acceptable and the unwind tables aren't worth paying for.

Verification

  • http.Server handler panic (the bex_str SWAR OOB, fully BAML-triggerable): under abort one bad request takes down the whole server process (exit 134); under unwind the panic is isolated, /healthz still returns 200, process survives.
  • Python end-to-end: calling the same panicking function through a release-bridge-python .so raises a catchable baml_core.errors.BamlPanic and the process survives (exit 0), instead of SIGABRT. Confirmed the built .so is unwind (__eh_frame 1.24 MB vs ~55 KB for abort).

Cost / notes

  • Engine compiles a second time in CI under unwind for these two artifacts (separate target dir).
  • ~+5 MB per SDK artifact (.so 13.2→18.3 MB). Not tracked by the size gate (which gates only baml-cli / packed-program / wasm).
  • napi --profile requires @napi-rs/cli v3 — pinned at 3.7.0.
  • Local dev (maturin develop, pnpm build:debug) already used the dev profile (unwind); only shipped artifacts were abort.
  • Does not fix the underlying SWAR panic itself — that's a separate correctness bug; this just makes any engine panic survivable instead of fatal.

Summary by CodeRabbit

  • Bug Fixes

    • Improved release builds for the Python and Node SDKs so engine panics are contained at the SDK boundary instead of terminating the host process.
  • Chores

    • Updated the Python wheel build to use a specialized release build profile.
    • Updated the Node.js N-API release build to use a matching specialized release build profile.
    • Enhanced release build configuration to improve runtime compatibility and stability.

The Python and Node SDKs load the engine as a native library inside the
user's own process. Under the release profile's panic="abort", a raw Rust
panic anywhere in the engine (e.g. a stdlib slice bounds check) calls
process::abort() and SIGABRTs the user's interpreter — their whole server
dies, uncatchable even by `except BaseException`.

The bridges already have the machinery to contain such a panic (a
catch_unwind -> baml.panics.SdkPanic boundary in bridge_cffi, and tokio's
per-task isolation in the in-engine HTTP server), but it only runs when the
stack can unwind, which abort forbids — so it was dead code in shipped wheels.

Add release-bridge-python / release-bridge-node profiles (release + panic=
unwind) and point the maturin and napi release builds at them. An engine
panic now surfaces as a catchable BamlPanic / JS error instead of killing
the host. The CLI and wasm stay on release/abort: each owns its own process
and is size-gated, so the ~5MB of unwind tables isn't worth paying there.

Verified: a BAML http.Server handler panic (the bex_str SWAR OOB) takes down
the whole process under abort (exit 134) but is isolated under unwind (server
keeps serving); and calling the same panicking function from Python through
the unwind .so raises a catchable BamlPanic and the process survives (exit 0).
@vercel

vercel Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Jun 17, 2026 12:27am
promptfiddle Ready Ready Preview, Comment Jun 17, 2026 12:27am
promptfiddle2 Ready Ready Preview, Comment Jun 17, 2026 12:27am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f6dc8f87-72ae-4943-8974-fff111b1ecef

📥 Commits

Reviewing files that changed from the base of the PR and between 54e46a0 and 4e997f9.

📒 Files selected for processing (1)
  • baml_language/Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • baml_language/Cargo.toml

📝 Walkthrough

Walkthrough

Two new Cargo profiles (release-bridge-python, release-bridge-node) are added, each inheriting release but overriding panic to unwind. The Python SDK CI workflow and the Node.js napi build script are updated to use these profiles instead of the generic --release flag.

Changes

Bridge SDK panic=unwind profiles

Layer / File(s) Summary
New Cargo bridge profiles
baml_language/Cargo.toml
Adds [profile.release-bridge-python] and [profile.release-bridge-node], each inheriting release but setting panic = "unwind", with inline comments documenting the panic-containment behavior at the Rust/bridge boundary.
CI and build script wiring
.github/workflows/build2-python-sdk.reusable.yaml, baml_language/sdks/nodejs/bridge_nodejs/package.json
Switches the maturin CI command to --profile release-bridge-python and the napi build:napi-release script to --profile release-bridge-node, replacing --release in both.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • BoundaryML/baml#3694: Modifies the Node.js N-API build scripts in baml_language/sdks/nodejs/bridge_nodejs/package.json, overlapping with this PR's update to the build:napi-release script.

Poem

🐇 A panic once crashed with a thud and a bang,
But now through the bridge it unwinds with a clang.
Two profiles were born—python, node—each just right,
To catch every wobble before the abort of fright.
The rabbit hops on, all wrapped up tight! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: switching Python/Node SDK builds to use panic=unwind instead of the default abort behavior. It directly reflects the core purpose of the changeset across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch avery/bridge-sdk-unwind-profile

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
vercel Bot temporarily deployed to Preview – beps June 17, 2026 00:03 Inactive
@github-actions

github-actions Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@github-actions

Copy link
Copy Markdown

No description provided.

@vercel
vercel Bot temporarily deployed to Preview – beps June 17, 2026 00:10 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 June 17, 2026 00:15 Inactive
@codeshaunted
codeshaunted added this pull request to the merge queue Jun 17, 2026
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle June 17, 2026 00:27 Inactive
Merged via the queue into canary with commit 1581adc Jun 17, 2026
52 checks passed
@codeshaunted
codeshaunted deleted the avery/bridge-sdk-unwind-profile branch June 17, 2026 00:34

This branch was previously deployed

3 inactive deployments
Preview – promptfiddle — 4e997f94 Deployed Jun 17, 2026 by vercel[bot]
Preview – promptfiddle2 — 4e997f94 Deployed Jun 17, 2026 by vercel[bot]
Preview – beps — 4e997f94 Deployed Jun 17, 2026 by vercel[bot]
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